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 as it would be in Java/C# (using C++ macros):
#define LOCK(mm_mutex) \
if(Lock lock{mm_mutex}) {} \
else
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...
:(
I was reading the whole thread, back then, and didn't even consider myself a C++ newbie! – Leonoraleonore