• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
kloffy

Critique my approach to writing RAII wrappers

19 posts in this topic

Recently, I have played around with wrapping up a couple of handy C libraries in order to have a nicer more modern C++11 programming interface. I am aware that there can be associated problems, however I still find RAII wrappers handy in many cases.

So, the straight forward approach would be something like this (untested):
class Texture
{
public:
	Texture():
		_id{0}
	{
		glGenTextures(1, &_id);
		
		if (!*this)
		{
			throw std::runtime_error("Failed to acquire texture.");
		}
	}
	
	~Texture()
	{
		if (*this)
		{
			glDeleteTextures(1, &_id);
		}
	}
	
	explicit operator bool() const
	{
		return _id != 0;
	}
	
	
	// Non-Copyable
	Texture(Texture const& other) = delete;
	Texture& operator=(Texture const& other) = delete;
	
	// Moveable
	Texture(Texture&& other):
		_id{0}
	{
		swap(*this, other);
	}
	Texture& operator=(Texture&& other)
	{
		swap(*this, other);
		return *this;
	}
	
	// Wrapper Methods
	//void bind();
	//void image(...);
	//voud subImage(...);
	
private:
	GLuint _id;
	
	friend void swap(Texture& lhs, Texture& rhs)
	{
		using std::swap;
		swap(lhs._id, rhs._id);
	}
};

Initially this works fine, but it tries to do things at the same time: provide wrapper methods and manage lifetime. This becomes a problem when you receive a handle from the C api, whose lifetime is already managed in some other way. In that case, you cannot use the wrapper methods. For example, this isn't possible:
Texture getBoundTexture()
{
	GLuint boundTexture = 0;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, (GLint*) &boundTexture);
	return {boundTexture};
}

Even if there was a matching constructor, this code would be buggy since now there would be two RAII objects managing one lifetime. When one of them is destroyed, the resource gets freed and the second destructor results in double deletion. Really, it should be possible to distinguish between objects that manage lifetimes and those that don't. Well, you might say there already is such a thing: smart pointers! I could create my Texture object on the heap and use smart pointers to manage its lifetime. However, there is really no need for creating objects on the heap, if instead we generalize smart pointers. In fact, this has been proposed. However, it is not part of the standard library yet and - what I think is even worse - it's not easily possible to associate wrapped methods with the handle.

So, instead I have come up with the following approach (which I am sure has its own problems, that's why I am looking for feedback):

First, lifetime management is encapsulated in a separate class (similar to the proposed unique_resource):
template<typename T>
class Unique
{
public:
	template<typename... Args>
	Unique(Args&&... args):
		_value{}
	{
		T::New(_value, std::forward<Args>(args)...);
		
		if (!_value)
		{
			throw std::runtime_error("Failed to acquire resource.");
		}
	}
	
	~Unique()
	{
		if (_value)
		{
			T::Delete(_value);
		}
	}
	
	Unique(Unique const& other) = delete;
	Unique& operator=(Unique const& other) = delete;
	
	Unique(Unique&& other):
		_value{}
	{
		swap(*this, other);
	}
	Unique& operator=(Unique&& other)
	{
		swap(*this, other);
		return *this;
	}
	
	T& 			operator*() 		{ return _value; }
	T const& 	operator*() const 	{ return _value; }
	
	T* 			operator->() 		{ return &_value; }
	T const* 	operator->() const 	{ return &_value; }
	
private:
	T _value;
	
	friend void swap(Unique& lhs, Unique& rhs)
	{
		using std::swap;
		swap(lhs._value, rhs._value);
	}
};

And the wrappers look like this:
class Texture
{
public:
	typedef GLuint Handle;
	
	static void New(Texture& object)
	{
		glGenTextures(1, &object._handle);
	}
	
	static void Delete(Texture& object)
	{
		glDeleteTextures(1, &object._handle);
	}
	
	Texture(): _handle{0} {}
	Texture(Handle const& handle): _handle{handle} {}
	
	Handle const& get() const { return _handle; }
	
	explicit operator bool() const
	{
		return _handle != 0;
	}
	
	// Wrapper Methods
	//void bind();
	//void image(...);
	//voud subImage(...);
	
private:
	Handle _handle;
	
	friend void swap(Texture& lhs, Texture& rhs)
	{
		using std::swap;
		swap(lhs._handle, rhs._handle);
	}
};

The usage could be as follows (artificial example):
{
	Texture bound = getBoundTexture(); // Imlementation as before, now works.

	Unique<Texture> managed;
	
	//Setup texture etc.
	//managed->image(...);
	
	managed->bind();
	
	// Draw Something

	bound.bind();
}

So, the wrappers are like a plain pointer, whereas if you want their lifetime managed, you should use Unique<Texture>. At the end of the block, the manage texture is destroyed, whereas the plain one is untouched. Of course, it would also be possible to implement a Shared<Texture> in the future.

Anyway, I am curious to hear your thoughts. Please critique away... Edited by kloffy
0

Share this post


Link to post
Share on other sites

Initializtion does not mean resource allocation.  Initialization means a minimal empty valid state.


First, I wanted to highlight that I intentionally did not implement RAII in the Texture class. It is merely wraps a plain handle and adds convenience wrappers. I might as well have called it TextureHandle or something like that. The RAII comes into play when you use this class in conjunction with Unique, which puts the object into a valid initialised state on construction. To sum it up: int* is not RAII, but std::unique_ptr<int> is RAII. Similarly, Texture is not RAII, but Unique<Texture> is RAII.

Edit: Upon re-reading your post, I may have misinterpreted it the first time. Are you suggesting that it should be possible to create a Unique<Texture> without creating an OpenGL texture object? I must say I tend to agree. I suppose creating it in an empty state and having a makeUnique method which calls Texture::New similar to those in the standard library would be preferable. Edited by kloffy
0

Share this post


Link to post
Share on other sites

 

RAII does not mean "allocate resources".

Whaaaat?
 
"Resource allocation is instantiation" does not mean resource allocation is instantiation?  What kind of wacky newspeak are we dealing with here?
 
Perhaps the simple answer is that if you can not control the lifetime of a resource, you can not use RAII to wrap it.  It's just not the appropriate tool for the job.

 

RAII is actually a total misnomer. The reason it's used has almost nothing to do with resource allocation (or indeed initialisation or instantiation) and everything to do with resource cleanup.

 

I remember reading in the C++ FAQ that a more accurate acronym would be DIRR (Destruction Is Resource Reclamation), but we're kinda stuck with RAII now.

1

Share this post


Link to post
Share on other sites

 

 

RAII does not mean "allocate resources".

Whaaaat?
 
"Resource allocation is instantiation" does not mean resource allocation is instantiation?  What kind of wacky newspeak are we dealing with here?
 
Perhaps the simple answer is that if you can not control the lifetime of a resource, you can not use RAII to wrap it.  It's just not the appropriate tool for the job.

 

RAII is actually a total misnomer. The reason it's used has almost nothing to do with resource allocation (or indeed initialisation or instantiation) and everything to do with resource cleanup.

 

I remember reading in the C++ FAQ that a more accurate acronym would be DIRR (Destruction Is Resource Reclamation), but we're kinda stuck with RAII now.

 

 

I vote this up 10 more times if I could.  RAII is about cleanup far more than it is about initialization.

0

Share this post


Link to post
Share on other sites

AII is actually a total misnomer. The reason it's used has almost nothing to do with resource allocation (or indeed initialisation or instantiation) and everything to do with resource cleanup.

That's not correct either, although you're right that it's a bit of a misnomer, and resource cleanup is the mandatory sequelae to resource acquisition in RAII.

 

RAII means the object invariant is ownership of a resource.  Once the object has been constructed, you have the resource, and once the object has been destroyed you no longer have the resource.  If at any point you do not have the resource and you have the object, the invariant has been contravened and you do not have RAII.  Similarly, if you have the resource but not the object, it's not RAII.

 

The point of the invariant is that it's a guarantee that the object == the resource.  Both ways. Always and forever.

 

If you are using deferred construction (as frob was discussing), you are not using RAII because it breaks the object invariant.

 

If you have singular objects or nullable objects (eg. smart pointers) you are not implementing RAII (although you can use std::unique_ptr to implement RAII if you remove the move operations).  It breaks the invariant.

 

Things that do the above are valid and useful, but they're not implementing RAII.  You're not using RAII just because you clean things up in your destructor: that's just basic object oriented programming.

 

So yes, RAII is very very much about resource acquisition on instantiation, and also about resource release on destruction.  Like NVI, the tag for the idea is not a complete description of what it means but if you're not acquiring the resource on object instantiation, you're not implementing "Resource Acquisition Is [Object] Instantiation".

Edited by Bregma
1

Share this post


Link to post
Share on other sites
Bregma: While true for some things, it is not universal.

There are many resources -- containers being the most obvious -- where "initialized" correctly means no allocations.

If you look back before RAII was a term that people thought about, the pattern was this:

// Acquisition. Gain a resource but the initial contents are whatever happened to previously be in memory
myObject = malloc(sizeof(sometype));
// Initialization. Put the resource in a known state
bzero(myObject, sizeof(sometype));

or this

// Acquisition. Gain a chunk of memory with unknown contents
myObject = malloc(sizeof(sometype));
// Initialization. Put the resource in a known state
memcpy(myObject, templateObject, sizeof(sometype), 1);

Object oriented languages improve on this with constructors that perform initialization for you. But even they are not perfect as some types do not initialize their values.

class Foo {
int x;
int y;
public:
Foo() { } // Both x and y are acquired but not intialized. Their contents could be anything.
...
}

RAII can be followed by initializing the object during construction:

Foo() : x(0), y(0) {} // RAII is followed, the object is initialized to a known state.


Yes, there are some classes where initialization absolutely requires additional allocations. But those situations in practice are extremely rare and are usually easily avoidable.

If a default constructor requires trips out for heap allocations you are probably doing something wrong. If a default constructor requires trips out to disk or across a network, you are absolutely doing something wrong. You can provide parameters for non-default constructors, but a default constructor should be instant and not consume additional resources beyond those to initialize the object to a known state.

As an example of a horrible case of default constructors doing this, I have seen beginner code where a default constructor would look in a known resource to look up the next item to load (in this case, an already open file). After reading the next line (potential round trip to disk could be quite long) it would open a file (which means the OS looks up a file name on potentially one or two disk reads, followed by another disk read to get the file). Then it would parse the file for names of sub-models and sub-textures, which in turn spawned off another series of reads and loads. All total using this default constructor required something on the order of 150 disk reads and 500 memory allocations. They were confused why allocating an array of these objects was slow.

Default constructors are used frequently in the language, and many default-constructed objects are allocated but never used. Requiring the default constructor to perform more work than simple initialization to a known state is often a serious implementation flaw.
1

Share this post


Link to post
Share on other sites

If you're not acquiring a resource in your constructor, you're not implementing RAII.  You're not (necessarily) writing bad code, you're just not implementing RAII.

 

If you have instantiated an object that does not acquire a resource, it is not implementing "resource acquisition is instantiation".  It might be wonderful, perfect code that does exactly what you want in the best way possible.  It's just not RAII.  You might initialize an object to a known state, and that's grand, but if the purpose of the object is to acquire a resource, and the object can exist without acquiring that resource, it's not implementing RAII.  Simply initializing an object to a known state and freeing any resources on object destruction isn't RAII, it's just general object-oriented programming with deterministic destruction.

 

If you use RAII, the statement "if the object exists, the resource is allocated" is always true.  Also, the statement "if the resource is allocated, the object exists" is true.  It is a boon to reasoning about program logic.  But the point of RAII is that you never have to reason something like "the object exists, and it may or may not have acquired resource X, so...."

 

Standard containers are not an example of RAII.  The standard smart pointers are not an example of RAII.

0

Share this post


Link to post
Share on other sites

if you're not acquiring the resource on object instantiation, you're not implementing "Resource Acquisition Is [Object] Instantiation".


and if RAII was "resource acquisition is instantiation", you'd be 100% correct.

 

But it's not; it's "resource acquisition is initialization". 

0

Share this post


Link to post
Share on other sites

Perhaps something is missing in translation but I'm having an hard time understanding how someone could initialize without instancing something first.

 

Honestly I found RAII, as well as standard OOP-inspired ownership semantics just inconvenient for code mangling hardware resources.

What I do: specify who holds ownership and ensure this.

RAII was just inconvenient in my last project, too many dependancies on the underlying APIs, too much code, enums, special cases... The rule of five? Are you kidding?

0

Share this post


Link to post
Share on other sites

Perhaps something is missing in translation but I'm having an hard time understanding how someone could initialize without instancing something first.
 
Honestly I found RAII, as well as standard OOP-inspired ownership semantics just inconvenient for code mangling hardware resources.
What I do: specify who holds ownership and ensure this.
RAII was just inconvenient in my last project, too many dependancies on the underlying APIs, too much code, enums, special cases... The rule of five? Are you kidding?


RAII lets you write something once that enforces ownership which then means you don't have to do it again. With RAII you let the computer enforce ownership and, to be honest, the computer is far better at enforcing (clear) rules then people are.

Sounds like you just had a bad experience with a bad API, as "too much code, enums, special cases" are all things that are not a part of RAII - or at least shouldn't be, aside from specifying ownership (who owns what, can ownership be shared, etc).

Also, the "rule of five" really is the Rule of Zero once you've written your RAII wrappers (which you've templated so you don't have to keep rewriting them, right?)

Let the compiler help you. It's what it's there for!
0

Share this post


Link to post
Share on other sites

I want to add something here - there's no particularly strong reason to generate or use exceptions in a game engine. That serves no productive purpose. Either assert on stuff that is fatal, or smoothly handle the problem in a way that can be detected and addressed.

 

On another note, I don't feel that the bool casts are a good idea either. Create a proper IsValid function.

 

 


In your case, any instance of a Texture object MUST allocate a graphics resource. If I were to attempt to create an array of texture objects for any purpose whatsoever I am obligated to also allocate OpenGL resources. If i were to, for example, make an array of 50 Texture objects so I could do something interesting, there is absolutely no way I can create these texture objects without also having GenTextures() run. 

To be fair, this is not quite how glGenTextures works; it creates a name reservation, which is effectively a variable increment inside the driver. Spooling up a bunch of names and not using them is not an impacting thing to do.

Edited by Promit
0

Share this post


Link to post
Share on other sites


it's "resource acquisition is initialization".

What is the class invariant of an object that can be in a valid state but not hold a resource?

 

What kind of aid to logic is "may or may not hold a resource"?

 

What kind of aid to reasoning is "the resource may or may not be held by this object"?

 

How should I distinguish between this version of RAII and any other object that gets initialized in its constructor and destroyed in its destructor?

 

How is late resource acquisition (or resource transfer)  different from open/close semantics?

 

Is there another name for the idiom in which the invariant of the object is "owns the resource"?

0

Share this post


Link to post
Share on other sites
I think we're getting just a tiny bit too nitpicky on the language (yes, I know, I helped start it tongue.png )

Stroustrup was the one who came up with the name, so I think it's best to let him explain it in his own words.

It's resource management, pure and simple. C++ uses constructors and destructors and the scope mechanism to make sure things that are initialized are cleaned up, and things that aren't initialized aren't (i.e. constructor threw an exception - which is the only way to get an error out of a constructor and maintain class invariants).

If I allocate/open/acquire a resource, I must deallocate/close/release said resource when I'm done with it. By allocating/opening/acquiring said resource in the constructor and saying that object "owns" that resource, then the destructor naturally deallocates/closes/releases that resource in all cases - normal or exceptional.

Does it solve all resource management issues? No. You still need to figure out your ownership. Some objects own uniquely (unique_ptr) some share ownership (shared_ptr). Use the right one for the job. Sometimes closing/releasing a resource can cause an error, in which case you want to have an explicit close/release function on the object because you cannot throw from a destructor. But the destructor is still there as a safety net (and should have a try/catch that eats any exceptions should the release have a chance to throw)

The standard library pointers and containers are RAII because they manage the lifetimes of their contents through new/delete/malloc/free in constructors and destructors. Just because they might be empty doesn't mean they don't implement RAII - just like how a null pointer is still a pointer.

TLDR: I know how to manage the lifetime of one object. But I will screw up the lifetime of ten thousand objects. Let the compiler handle that after I tell it how to manage the lifetime of a single one and my code will be much more stable, bug free, and more secure.
0

Share this post


Link to post
Share on other sites

So this thread went from "How is my RAII" to "What the hell is RAII". Interesting comments.

 

 

Barney says:

 

"The name of the game here is to get allocation out of the way so you don't see it"

 

"the main tool for resource management is constructors and destructors"

 

A standard container that, say, resizes itself during its lifetime (allocating/deallocating memory outside of constructor/destructor) would therefore not be RAII, right? Or is that being too literal? 

 

To me, if what Bregma says isn't true, then RAII doesn't mean anything new, it is just a reminder to program properly. Sussing out ownership is just sane programming. 

 

A practical application could do wonders for this debate. I approach from the perspective of someone with a resource intensive program -- once I've instantiated the RAII-abiding objects I need without issue, I can count on having the resources ready, ie, the program won't fail on a resource allocation after that point. Could be memory, a serial port, an electron gun, drone...they are all ready to go. My initial interpretation of RAII was along these lines.

0

Share this post


Link to post
Share on other sites

So this thread went from "How is my RAII" to "What the hell is RAII". Interesting comments.

Yes, I contemplated whether I should step in, but I figured I will just let the discussion go. I don't really see the point of trying to (re-)define what RAII is. As SmkViper pointed out, the man who invented it has written extensively about it. Sure, there are nuances and different interpretations, but what I am interested in is a workable solution for wrapping C libraries like SDL, OpenGL, etc.
0

Share this post


Link to post
Share on other sites


RAII isn't a new term

 

yeah, I should have said "new to me"...just didn't really pay much attention before C11 and have been confused by the literature...lazy maybe...backing out...

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0