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.
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
FileHandleclass employing RAII: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:
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,
Foowould 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:
The constructor of
Loggerwill fail iforiginal‘s constructor fails (becausefilename1could not be opened),duplex‘s constructor fails (becausefilename2could not be opened), or writing to the files insideLogger‘s constructor body fails. In any of these cases,Logger‘s destructor will not be called – so we cannot rely onLogger‘s destructor to release the files. But iforiginalwas constructed, its destructor will be called during cleanup of theLoggerconstructor.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 ofLogger, 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
IDisposablejust 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.