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 dues 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 when ever I return".
Lets start with an simplistic FileHandle class employing RAII:
// an overly simplistic file handle, employing RAII:
class FileHandle
{
public:
FileHandle(char * name)
{
h = fileopen(name);
if (!h)
throw "MAYDAY! MAYDAY";
}
~FileHandle() { fileclose(h); }
// ...
}
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, 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, lets aggregate some objects:
class Logger
{
FileHandle h, hDuplex; // this logger can write to two files at once!
public:
Logger(char const * file, const char * duplex) : h(file), hDuplex(duplex)
{
if (!filewrite_duplex(h, hDuplex, "New Session"))
throw "Ugh damn!";
}
}
The constructor of Logger will fail if h can't be opened, hDuplex can't be opened, or writing to the files fails. In any of these cases, Logger's destructor will not be called - so we can't rely on Loggers destructor to release the files). But if h was opened, its destructor will be called during cleanup of the constructor call.
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(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 handles destruction appropriately.)
I've never wished for garbage collection really. When I do C# I sometimes feel a moment of bliss that I just don't need to care, but much more I miss all the cool toys that can be created through deterministic destruction. (using/IDisposable just doesn't cut it.)
I've had one particularly complex structure that might have benefitted 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.