Understanding the meaning of the term and the concept - RAII (Resource Acquisition is Initialization)
Could you C++ developers please give us a good description of what RAII is, why it is important, and whether or not it might have any relevance to other languages?
I do know a little bit. I believe it stands for "Resource Acquisition is Initialization". However, that name doesn't jive with my (possibly incorrect) understanding of what RAII is: I get the impression that RAII is a way of initializing objects on the stack such that, when those variables go out of scope, the destructors will automatically be called causing the resources to be cleaned up.
So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)? How do you get from there to "RAII"?
And how can you make something on the stack that will cause the cleanup of something that lives on the heap? Also, are there cases where you can't use RAII? Do you ever find yourself wishing for garbage collection? At least a garbage collector you could use for some objects while letting others be managed?
Thanks.
So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)?
RAII is telling you what to do: Acquire your resource in a constructor! I would add: one resource, one constructor. UTSTTC is just one application of that, RAII is much more.
Resource Management sucks. Here, resource is anything that needs cleanup after use. Studies of projects across many platforms show the majority of bugs are related to resource management - and it's particularly bad on Windows (due to the many types of objects and allocators).
In C++, resource management is particularly complicated due to the combination of exceptions and (C++ style) templates. For a peek under the hood, see GOTW8).
C++ guarantees that the destructor is called if and only if the constructor succeeded. Relying on that, RAII can solve many nasty problems the average programmer might not even be aware of. Here are a few examples beyond the "my local variables will be destroyed whenever I return".
Let us start with an overly simplistic FileHandle
class employing RAII:
class FileHandle
{
FILE* file;
public:
explicit FileHandle(const char* name)
{
file = fopen(name);
if (!file)
{
throw "MAYDAY! MAYDAY";
}
}
~FileHandle()
{
// The only reason we are checking the file pointer for validity
// is because it might have been moved (see below).
// It is NOT needed to check against a failed constructor,
// because the destructor is NEVER executed when the constructor fails!
if (file)
{
fclose(file);
}
}
// The following technicalities can be skipped on the first read.
// They are not crucial to understanding the basic idea of RAII.
// However, if you plan to implement your own RAII classes,
// it is absolutely essential that you read on :)
// It does not make sense to copy a file handle,
// hence we disallow the otherwise implicitly generated copy operations.
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// The following operations enable transfer of ownership
// and require compiler support for rvalue references, a C++0x feature.
// Essentially, a resource is "moved" from one object to another.
FileHandle(FileHandle&& that)
{
file = that.file;
that.file = 0;
}
FileHandle& operator=(FileHandle&& that)
{
file = that.file;
that.file = 0;
return *this;
}
}
If construction fails (with an exception), no other member function - not even the destructor - gets called.
RAII avoids using objects in an invalid state. it already makes life easier before we even use the object.
Now, let us have a look at temporary objects:
void CopyFileData(FileHandle source, FileHandle dest);
void Foo()
{
CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}
There are three error cases to handled: no file can be opened, only one file can be opened, both files can be opened but copying the files failed. In a non-RAII implementation, Foo
would have to handle all three cases explicitly.
RAII releases resources that were acquired, even when multiple resources are acquired within one statement.
Now, let us aggregate some objects:
class Logger
{
FileHandle original, duplex; // this logger can write to two files at once!
public:
Logger(const char* filename1, const char* filename2)
: original(filename1), duplex(filename2)
{
if (!filewrite_duplex(original, duplex, "New Session"))
throw "Ugh damn!";
}
}
The constructor of Logger
will fail if original
's constructor fails (because filename1
could not be opened), duplex
's constructor fails (because filename2
could not be opened), or writing to the files inside Logger
's constructor body fails. In any of these cases, Logger
's destructor will not be called - so we cannot rely on Logger
's destructor to release the files. But if original
was constructed, its destructor will be called during cleanup of the Logger
constructor.
RAII simplifies cleanup after partial construction.
Negative points:
Negative points? All problems can be solved with RAII and smart pointers ;-)
RAII is sometimes unwieldy when you need delayed acquisition, pushing aggregated objects onto the heap.
Imagine the Logger needs a SetTargetFile(const char* target)
. In that case, the handle, that still needs to be a member of Logger
, needs to reside on the heap (e.g. in a smart pointer, to trigger the handle's destruction appropriately.)
I have never wished for garbage collection really. When I do C# I sometimes feel a moment of bliss that I just do not need to care, but much more I miss all the cool toys that can be created through deterministic destruction. (using IDisposable
just does not cut it.)
I have had one particularly complex structure that might have benefited from GC, where "simple" smart pointers would cause circular references over multiple classes. We muddled through by carefully balancing strong and weak pointers, but anytime we want to change something, we have to study a big relationship chart. GC might have been better, but some of the components held resources that should be release ASAP.
A note on the FileHandle sample: It was not intended to be complete, just a sample - but turned out incorrect. Thanks Johannes Schaub for pointing out and FredOverflow for turning it into a correct C++0x solution. Over time, I've settled with the approach documented here.
There are excellent answers out there, so I just add some things forgotten.
0. RAII is about scopes
RAII is about both:
- acquiring a resource (no matter what resource) in the constructor, and un-acquiring it in the destructor.
- having the constructor executed when the variable is declared, and the destructor automatically executed when the variable goes out of scope.
Others already answered about that, so I won't elaborate.
1. When coding in Java or C#, you already use RAII...
MONSIEUR JOURDAIN: What! When I say, "Nicole, bring me my slippers, and give me my nightcap," that's prose?
PHILOSOPHY MASTER: Yes, Sir.
MONSIEUR JOURDAIN: For more than forty years I have been speaking prose without knowing anything about it, and I am much obliged to you for having taught me that.
— Molière: The Middle Class Gentleman, Act 2, Scene 4
As Monsieur Jourdain did with prose, C# and even Java people already use RAII, but in hidden ways. For example, the following Java code (which is written the same way in C# by replacing synchronized
with lock
):
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
... is already using RAII: The mutex acquisition is done in the keyword (synchronized
or lock
), and the un-acquisition will be done when exiting the scope.
It's so natural in its notation it requires almost no explanation even for people who never heard about RAII.
The advantage C++ has over Java and C# here is that anything can be made using RAII. For example, there are no direct build-in equivalent of synchronized
nor lock
in C++, but we can still have them.
In C++, it would be written:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
which can be easily written the Java/C# way (using C++ macros):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII have alternate uses
WHITE RABBIT: [singing] I'm late / I'm late / For a very important date. / No time to say "Hello." / Goodbye. / I'm late, I'm late, I'm late.
— Alice in Wonderland (Disney version, 1951)
You know when the constructor will be called (at the object declaration), and you know when its corresponding destructor will be called (at the exit of the scope), so you can write almost magical code with but a line. Welcome to the C++ wonderland (at least, from a C++ developer's viewpoint).
For example, you can write a counter object (I let that as an exercise) and use it just by declaring its variable, like the lock object above was used:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
which of course, can be written, again, the Java/C# way using a macro:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3. Why does C++ lack finally
?
[SHOUTING] It's the final countdown!
— Europe: The Final Countdown (sorry, I was out of quotes, here... :-)
The finally
clause is used in C#/Java to handle resource disposal in case of scope exit (either through a return
or a thrown exception).
Astute specification readers will have noticed C++ has no finally clause. And this is not an error, because C++ does not need it, as RAII already handle resource disposal. (And believe me, writing a C++ destructor is magnitudes easier than writing the right Java finally clause, or even a C#'s correct Dispose method).
Still, sometimes, a finally
clause would be cool. Can we do it in C++? Yes, we can! And again with an alternate use of RAII.
Conclusion: RAII is a more than philosophy in C++: It's C++
RAII? THIS IS C++!!!
— C++ developer's outraged comment, shamelessly copied by an obscure Sparta king and his 300 friends
When you reach some level of experience in C++, you start thinking in terms of RAII, in terms of construtors and destructors automated execution.
You start thinking in terms of scopes, and the {
and }
characters become ones of the most important in your code.
And almost everything fits right in terms of RAII: exception safety, mutexes, database connections, database requests, server connection, clocks, OS handles, etc., and last, but not least, memory.
The database part is not negligible, as, if you accept to pay the price, you can even write in a "transactional programming" style, executing lines and lines of code until deciding, in the end, if you want to commit all the changes, or, if not possible, having all the changes reverted back (as long as each line satisfy at least the Strong Exception Guarantee). (see the second part of this Herb's Sutter article for the transactional programming).
And like a puzzle, everything fits.
RAII is so much part of C++, C++ could not be C++ without it.
This explains why experienced C++ developers are so enamored with RAII, and why RAII is the first thing they search when trying another language.
And it explains why the Garbage Collector, while a magnificient piece of technology in itself, is not so impressive from a C++ developer's viewpoint:
- RAII already handles most of the cases handled by a GC
- A GC deals better than RAII with circular references on pure managed objects (mitigated by smart uses of weak pointers)
- Still A GC is limited to memory, while RAII can handle any kind of resource.
- As described above, RAII can do much, much more...
RAII is using C++ destructors semantics to manage resources. For example, consider a smart pointer. You have a parameterized constructor of the pointer that initializes this pointer with the adress of object. You allocate a pointer on stack:
SmartPointer pointer( new ObjectClass() );
When the smart pointer goes out of scope the destructor of the pointer class deletes the connected object. The pointer is stack-allocated and the object - heap-allocated.
There are certain cases when RAII doesn't help. For example, if you use reference-counting smart pointers (like boost::shared_ptr) and create a graph-like structure with a cycle you risk facing a memory leak because the objects in a cycle will prevent each other from being released. Garbage collection would help against this.
I'd like to put it a bit more strongly then previous responses.
RAII, Resource Acquisition Is Initialization means that all acquired resources should be acquired in the context of the initialization of an object. This forbids "naked" resource acquisition. The rationale is that cleanup in C++ works on object basis, not function-call basis. Hence, all cleanup should be done by objects, not function calls. In this sense C++ is more-object oriented then e.g. Java. Java cleanup is based on function calls in finally
clauses.
I concur with cpitis. But would like to add that the resources can be anything not just memory. The resource could be a file, a critical section, a thread or a database connection.
It is called Resource Acquisition Is Initialization because the resource is acquired when the object controlling the resource is constructed, If the constructor failed (ie due to an exception) the resource is not acquired. Then once the object goes out of scope the resource is released. c++ guarantees that all objects on the stack that have been successfully constructed will be destructed (this includes constructors of base classes and members even if the super class constructor fails).
The rational behind RAII is to make resource acquisition exception safe. That all resources acquired are properly released no matter where an exception occurs. However this does rely on the quality of the class that acquires the resource (this must be exception safe and this is hard).