I tried really hard to come up with some witty title or pun to weave into the title of this post. I couldn’t. RAII is just a terrible name, and it isn’t really clever or funny. Unfortunately, it is also the single most important key to C++. It is not just an idiom but a fundamental philosophy used to solve almost any problem in the language. So we can’t really avoid it.
If I had to pinpoint one thing that marked the difference between a skilled and an unskilled C++ programmer, it would be “do they understand RAII”. Many people don’t, hence this post.
RAII is, apart from being badly named, one of those deceptively simple concepts that you think you understand when you first hear of it, think “well duh, that’s obvious”, and then proceed to write code as usual, because you just don’t see how widely applicable it is.
But let’s get the name out of the way first. RAII stands for “Resource Acquisition Is Initialization”. And if you’re not already familiar with the idiom, then this has told you nothing at all. If you did know about RAII in advance, then you can, when you stop and think about it, kind of see how the name relates to it… vaguely… sort of.
What it actually means is simple: Resources should be managed by classes. When the class is initialized, the resource is acquired (hence the name). When the class is destroyed, the resource is released. And the lifetime of the object should exactly match the desired lifetime of the resource. That sounds obvious, and many programmers will (assuming they’re working in a language that has classes), say that this is what they always do.
Often, C++ developers think this just means “smart pointers. Wrap your memory allocation in a boost::shared_ptr and you’re done”. I see that as one not-very-often used border case though, rather than a typical example of RAII. So let’s take a step back instead.
The key idea isthat any kind of resource, not just memory, but file handles, sockets, database connections, or even more abstract resources like loggers or profiling timers or textures, really any concept or process which has a lifetime, should be mapped to an object.
Unlike the typical object-oriented line of thought which goes that “everything must be an object, because then.… well, everything will be an object, and your code will be better”, here we actually have a concrete reason: We want to use the object to manage the lifetime of the resource.
When I allocate memory with new, I have to deallocate it again sooner or later, with delete. (Or in C, with malloc() and free() respectively). And I have to make sure that this is done. And I have to make sure that it is not done twice. And that the object is not accessed after this is done. There are a lot of constraints we have to obey, all related to the lifetime of the resource. And this is why unmanaged programs have a reputation of leaking memory left and right. If we allocate memory, and it is to be used by a dynamic number of objects or functions all referencing the same allocations, which of the users is responsible for deleting it? And how do we know when it is safe to delete, when no users remain?
Ironically, most managed languages have not solved the problem. They have added a garbage collector (which yes, is very useful for a wide number of reasons), but that only solves one specific instance of the problem. It takes care of avoiding memory leaks, but it doesn’t avoid resource leaks in general.
The garbage collector ensures that this code won’t leak memory:
void foo() {
SomeObject* obj = new SomeObject();
bar(obj);
}
where without a garbage collector, we’d (at least without RAII) have to write code such as
void foo() {
SomeObject* obj = new SomeObject();
try {
bar(obj);
delete obj;
}
catch(...){ delete obj; }
}
In the garbage collected case, we don’t know what bar does, and we don’t need to know. It doesn’t have to delete the object. And neither does the foo function. So we have successfully dodged the problem of managing the lifetime of memory allocations. We haven’t really solved the problem though. We still don’t have any good tools to manage the lifetime. We’re just guaranteed by the system that it’ll last long enough.
In C++, this effect can be approximated using some kind of smart pointer1.
Smart pointers allow us to write code like this:
void foo() {
boost::shared_ptr<SomeObject> ptr = new SomeObject();
bar(ptr);
}
and be sure we won’t leak memory. Of course, this solution isn’t perfect — reference counting is much more expensive than a good garbage collector, and if we create cyclic references, the objects will never be deleted, as the reference counts never reach zero. It is a decent approximation, but nowhere near as good and reliable as the garbage collector in managed languages.
But the problem shows up again if we use another type of resource. What if we’d opened a database connection instead? We’d have to write code such as this: (The following Java-like pseudocode is copied almost verbatim from this StackOverflow.com answer, courtesy of Martin York.)
void writeToDb()
{
Db db = new Db("DBDesciptionString");
try
{
// Use the db object.
}
finally
{
db.close();
}
}
(And of course it gets even worse if db.close() can throw exceptions. Then we have to catch that exception, just to avoid it propagating out from the finally clause if we reached finally because of an exception being thrown in the try clause.)
The resource management problem still exists. We still have to wrap the code in exception handling just to make sure that the connection is closed as soon as we’re done with it. And we have to do this at every use. And it gets complicated fast.
Of course, .NET makes this a bit simpler:
using (Db db = new Db("DbDescriptionString"))
{
// use the database object.
}
But the onus is still on the user of the class to ensure it is closed correctly. There is no obvious way to encode into the Db class that “once we’re done with an object of this type, the connection must be closed immediately”.
And in C++, smart pointers are no longer suitable solutions, since the resource to be managed is no longer a pointer allocated with new.
Instead, a more basic flavor of RAII comes to the fore:
void someFunc()
{
Db db("DBDesciptionString");
// Use the db object.
}
Yes, that’s all. When the db object goes out of scope, at the end of the function, its destructor is called. The destructor internally calls this->Close() for us, so we don’t need to do it! We just have to trust the scoping rules of C++, which guarantee that destructors are called on local variables when they go out of scope, and on class members when the class is destroyed.
So in a sense, the key idea in RAII is simply that “resources should behave sensibly”. They should get copied safely if an assignment is made (or otherwise, assignments should be prevented), they should be available if their owning object is successfully created (if it can’t create the resource, it should throw an exception, aborting the creation of the object), and when they are no longer used, they should be cleaned up.
The C++ standard library class template std::vector is a wonderful example of RAII in action. The resources being managed by a vector are memory (the array allocated internally to hold the objects being contained in the vector, as well as the objects themselves. When the vector is destroyed, every object it holds must be destroyed too, and the array in which they were placed must be deallocated.
In the following examples, assume that a function foo is passed a vector of MyClass objects by value. We don’t know how many, if any, objects are stored in it, but since we are passed a copy of the original vector, we take ownership of it. It exists only in the function foo, and must be destroyed afterwards.
void foo(std::vector<MyClass> vec) {
...
// when we get to the end of the function, all local variables, including vec,
// are automatically destroyed by having their destructors invoked.
// So no matter how many MyClass objects were stored in the vector, it ensures that they too have their destructors called.
// And the vector also deallocates its internal array, leaving neither of its resources alive at the end of the function
}
void foo(std::vector<MyClass> vec) {
throw std::exception("Oops");
// as above, vec is automatically destroyed when we leave the function,
// regardless of *how* we leave it. Even if we leave it because an exception was thrown and not caught.
}
void foo(std::vector<MyClass> vec) {
// other is constructed as a copy of vec. std::vector ensures that both of vecs resources are copied as well
std::vector<MyClass> other = vec;
// we now have two vectors, each owning a dynamically allocated array and a number of MyClass objects
// and again, at the end of the function, both are deallocated cleanly
}
void foo(std::vector<MyClass> vec) {
std::vector<MyClass> other; // a second, empty, vector
// perform an assignment, setting vec to be an empty vector
// std::vector makes sure that if you do this, the resources previously held by vec are cleanly released
// before copies are made of the resources held by other
vec = other;
// and so when the function ends, the MyClass objects originally held by vec
// have already been destroyed, so their destructors are *not* invoked now
}
As the above shows, vec owns its resources, and manages them tightly. Whenever a change happens to vec, it reflects this by updating its owned resources. If it is destroyed, it destroys its owned resources. If it is copied, it copies the resources it owns. If it is assigned to hold something else, it first destroys its existing resources. And so on. Nothing you do can bring it “out of balance”. It just works. That is RAII. Smart pointers are just convenient adapters turning raw pointers into RAII objects. But RAII is much more than smart pointers.
It is the broad and general idea that resources should be mapped to objects, so that the object can not be created unless it succeeded in acquiring its resource, and it can not be destroyed without also releasing its resource. This effectively saves C++ programmers from having to worry about resource management.
Take an example that’s guaranteed to cause pain without the use of RAII: Handling exceptions being through halfway through constructors. Say you have a class with multiple members which are initialized in its constructor. After the first member has been initialized, but before all of them have been initialized, an exception is thrown. Let’s use the following contrived example:
class Foobar {
Foo f;
Bar b;
MyClass c;
public:
Foobar() : f(42), b("hello world), c('a') {}
};
unfortunately, b’s constructor throws an exception. How to handle this? We know that in C++, partially constructed objects do not automatically have their destructors called. when the construction is aborted.
And since we want to avoid any resource leaks, we require that the following must happen: – a must have its destructor called (because a was successfully initialized before the error occurrd) – b must release any resources it acquired in its constructor before it threw the exception – c must do nothing. Its construction was not yet begun when the error ocurred, so it would be an error to attempt any kind of cleanup of c. – The Foobar object (the object pointed to by the this pointer) must ensure that the above, and nothing else, happens, and it must do so without relying on its own destructor (which won’t be called, as construction did not successfully complete).
And of course, pretending that only b can throw an exception may be a simplification over the real world. Perhaps every member could throw one from its constructor. Care to write a Foobar constructor which takes all this into account, providing enough try/catch blocks to correctly catch every exception that might be thrown, and release exactly the resources that have been allocated until then, and nothing else? A tall order, and an open invitation for bugs. And of course, it’d lead to a huge, bloated and error-prone constructor. It’d also prevent us from using the initializer list. We’d have to perform some kind of “safe” non-throwing default construction of both a, b and c before entering the constructor body, where exception handling is possible, and from there, attempt to perform assignments to bring the three members into the desired state.
In pseudocode, the constructor might look something like this:
Foobar() {
a = new Foo(42);
try {
b = new Bar("hello world");
}
catch {
destroy a;
throw;
}
try {
c = new MyClass();
}
catch {
destroy b;
destroy a;
throw;
}
}
Note that all this complexity is only necessary because we want to handle several different resources. a, b and c all contain resources that must be attempted acquired, and properly released if this fails. If there’d been only one resource, the job would have been much simpler. There wouldn’t be any point at which some resources have been acquired, and others have not. If we succeeded in acquiring that one resource, there’d be no risk of errors occurring afterwards, so we wouldn’t need complex conditional cleanup code. And if we failed to acquire the one resource, there’d be nothing to clean up — after all, the resource was never acquired!
So to keep down the complexity, the only safe way to define a class is to make it own at most one resource. And this one-to-one mapping of resources to classes is exactly what RAII is all about. If a, b and c had all been RAII objects, then the above code would work. Regardless of which members could or couldn’t throw exceptions. According to the rules of C++, we know that in the above case,
- the
Foobardestructor (this->Foobar::~Foobar()will not be called, as*thiswas not successfully constructed. - the
adestructor will be called, as this member was fully constructed at the time of the error. - the
bandcdestructors will not be called, as these members were not fully constructed at the time of the error.
So assuming that b’s constructor takes care of releasing any resources successfully allocated when the error occurred (the number of which, as pointed out above, should ideally be zero), we’re actually home free! What happens is exactly what we listed earlier as our goal. a has its destructor called, c’s constructor was never run in the first place, so it doesn’t have to do anything, and *this doesn’t have to do anything special in its constructor. All of its members take care of their own resources, so the number of resources managed by *this is zero!
We don’t even need to write a destructor for Foobar now, if all its members are RAII objects. Whether the Foobar object is partially or fully constructed, its members take care of themselves. That is the power of RAII. Once a resource has been mapped to a class, we can use it as much as we like, and even in very complex situations, and never have to worry about the resource being leaked. It is managed by its wrapping RAII object, and the C++ lifetime and scope rules ensure that this wrapper object gets destroyed when it goes out of scope
-
A smart pointer is an object which behaves as a pointer (meaning that it overloads the
*and->operators, so it can be dereferenced to yield the pointed-to value), but also enforces some kind of ownership semantics on the value. A plain pointer does nothing when it goes out of scope. If it pointed to some dynamically allocated memory, nothing happens to that memory. And if no one else have a pointer to it, then that memory is lost, and can not be reclaimed. A smart pointer does something when it is destroyed. Some variants simply free the memory they point to (boost::scoped_ptr,std::auto_ptrorstd::unique_ptrall fall into this category, although with some important differences), while others implement reference counting, so that the memory is only destroyed when all smart pointers pointing to it have been destroyed.boost::shared_ptris by far the best known implementation of this concept. ↩





[…] still be confused by this idiom. One thing I read while keeping tabs on the web for C++ articles is this one from […]
GREAT ARTICLE! I felt like it was directed exactly where I am at in my journey to learn more about OOP. I think I know how to talkabout RAII better: resources are mapped to objects.
Didn’t know that garbage collectors were less resource-intensive than shared_ptrs; I thought they were about the same, since they did about the same thing.
They do roughly the same thing, yes, but in very different ways.
Consider that a reference counter has to be updated every time a reference is created or deleted. If a smart pointer points to object
a, and you set it to point tobinstead, you have to update the reference counters for both objects. And every update has to be done atomically to ensure thread safety as well. That makes it still more costly. When you add it up like that, it’s actually quite a few CPU cycles that are thrown away updating reference counters.By comparison, a garbage collector doesn’t have to do anything when references are created, modified or destroyed. It only has to step in when the heap has been filled so much that a memory allocation fails. Once that happens, it has to traverse the graph of live objects, marking each as in use. All dead (nonreachable) objects are never even touched by the GC; so they’re effectively free. Traversing this graph does take a bit of time, as does the heap compaction that typically follows. But because it happens so rarely compared to ref counting, it’s still vastly cheaper overall.