February 22, 2016 at 06:36 Tags C & C++

I've read a random quote online about "RAII in C++ is only possible with exceptions" once too much. I can't take it any more.

TL; DR: this post is not about whether exceptions are good or bad. What it is about is RAII as a C++ dynamic resource management technique that stands on its own and is useful with or without exceptions. In particular, I want to explain why RAII is indeed useful even if you have exceptions disabled in your C++ code.

The basics Let's take the poster child of RAII, an auto-closing handle to wrap FILE* : class FileHandle { public : FileHandle ( const char * name , const char * mode ) { f_ = fopen ( name , mode ); } FILE * file () { return f_ ; } ~ FileHandle () { if ( f_ != nullptr ) { fclose ( f_ ); } } private : FILE * f_ ; }; Here's an example of how we'd use it: std :: string do_stuff_with_file ( std :: string filename ) { FileHandle handle ( filename . c_str (), "r" ); int firstchar = fgetc ( handle . file ()); if ( firstchar != '$' ) { return "bad bad bad" ; } return std :: string ( 1 , firstchar ); } Remember: no exceptions here - the code is built with -fno-exceptions and there are no try statements. However, the RAII-ness of FileHandle is still important because do_stuff_with_file has two exit points, and the file has to be closed in each one. do_stuff_with_file is a short and simple function. In a larger function with multiple exit points managing resource release becomes even more error prone, and RAII techniques are paramount. The essence of RAII is to acquire some resource in the constructor of a stack-allocated object, and release it in the destructor. The compiler guarantees that the destructors of all stack-allocated objects will be called in the right order when these objects go out of scope, whether due to raised exceptions or just because the function returns. RAII doesn't mean you have to allocate or actually create anything in a constructor. It can do any operation that has a logical "undo" that must be performed later on. A good example is reference counting. Many databases and similar software libraries have abstractions of "cursors" that provide access to data. Here's how we could increase and decrease the reference count on a given cursor safely while working with it: class CursorGuard { public : CursorGuard ( Cursor * cursor ) : cursor_ ( cursor ) { cursor_ -> incref (); } Cursor * cursor () { return cursor_ ; } ~ CursorGuard () { cursor_ -> decref (); } private : Cursor * cursor_ ; }; void work_with_cursor ( Cursor * cursor ) { CursorGuard cursor_guard ( cursor ); if ( cursor_guard . cursor () -> do_stuff ()) { // ... do something return ; } // ... do something else return ; } Once again, usage of RAII here ensures that under no circumstances work_with_cursor will leak a cursor reference: once incref'd, it is guaranteed to be decref's no matter how the function ends up returning.

RAII in the standard library Such "guard" RAII classes are extremely useful and widespread, even in the standard library. The C++11 threading library has lock_guard for mutexes, for example: void safe_data_munge ( std :: mutex & shared_mutex , Data * shared_data ) { std :: lock_guard < std :: mutex > lock ( shared_mutex ); shared_data -> munge (); if (...) { shared_data (); return ; } shared_data -> munge_less (); return ; } std::lock_guard locks the mutex in its constructor, and unlocks it in its destructor, ensuring that access to the shared data is protected throughout safe_data_munge and the actual unlocking always happens.

RAII and C++11 While on the topic of the standard library, I can't fail mentioning the most important RAII object of them all - std::unique_ptr . Resource management in C and C++ is a big and complex subject; the most common kind of resource managed in C++ code is heap memory. Prior to C++11, there were many third party solutions for "smart pointers", and C++11's move semantics finally allowed the language to have a very robust smart pointer for RAII: void using_big_data () { std :: unique_ptr < VeryVeryBigData > data ( new VeryVeryBigData ); data -> do_stuff (); if ( data -> do_other_stuff ( 42 )) { return ; } data -> do_stuff (); return ; } Whatever we do with data , and no matter where how function returns, the allocated memory will be released. If your compiler supports C++14, the line that creates the pointer can be made more succinct with std::make_unique : // Good usage of 'auto': removes the need to repeat a (potentially long) // type name, and the actual type assigned to 'data' is trivially obvious. auto data = std :: make_unique < VeryVeryBigData > (); std::unique_ptr is versatile and has other uses, though here I'm just focusing on its value as a RAII enabler for heap memory. To to stress how important C++11 is for proper RAII: prior to C++11, without move semantics, the only "smart" pointers we could write were really somewhat dumb because they led to too much copying and overhead. There was simply no way to "transfer ownership" of an object from one function to another without considerable overhead. Since C++ programmers are often all about squeezing the last bit of performance from their code, many preferred to just live on the edge and deal with raw pointers. With C++11 and std::unique_ptr , which can be efficiently moved and occupies no additional memory, this problem is much less serious and safety doesn't have to come at the price of performance.

RAII in other languages A common question asked about C++ is "why doesn't C++ have the finally construct enjoyed by other languages like Java, C# and Python?". The answer, given by Stroustrup himself is that RAII is a replacement. Stroustrup reasons (rightly, IMHO) that in realistic codebases there are far more resource acquisitions and releases than distinct "kinds" of resources, so RAII leads to less code. Besides, it's less error prone since you code the RAII wrapper once and don't have to remember to release the resource manually. Here's the work_with_cursor sample from above rewritten with a hypothetical finally construct: // Warning: this is not real C++ void work_with_cursor ( Cursor * cursor ) { try { cursor -> incref (); if ( cursor -> do_stuff ()) { // ... do something return ; } // ... do something else return ; } finally { cursor -> decref (); } } Yes, it's a bit more code. But the bigger problem is remembering to call cursor-decref() . Since large codebases juggle resources all the time, in practice you'll end up with try...finally blocks around every function's body and having to remember which resources to release. With our CursorGuard helper, all of that is saved at the cost of a one-time definition of the guard class itself. A good example to mention here is Python. Even though Python has a finally construct, in modern Python code the alternative with statement is much more widely used. with supports "context managers", which are very similar to C++ RAII. with statements end up being more versatile and nice to use than finally , which is why you'll see more of them in idiomatic code.