Resource Aquisition Is Initialization, or RAII for short, is a powerful programming technique designed to help simplify programming. It's especially useful in languages which do not implement Garbage Collection, by avoiding the memory leaks bound to occur in the errors of doing everything by hand.
The Basic Concept
The basic concept is simple enough — an object is responsible for the lifetime and destruction of the resources it owns. When the object is destroyed, an automated mechanism such as a Finalizer or Destructor automatically does the same for it's children.
RAII in C++
Standard Library Examples
If you've programmed in C++, chances are you've already used RAII. std::[i/o]fstream, the basis for C++ file input and output, are the most obvious example. When the file stream is destroyed, the file handle is automatically closed — no manual call to .close() is needed. Contrast to the C API FILE* handles, which must be manually fclose()d:
//C++ style RAII:
void example1() {
std::ofstream todo( "todo.txt" );
todo << "Buy some milk" << std::endl;
//todo.txt is automatically closed when the todo variable goes out of scope and
//is destroyed.
}
//Non-RAII C-style equivilant:
void example2() {
FILE * todo = fopen( "todo.txt" , "w" );
fputs( todo , "Buy some milk\n" );
//todo.txt must manually be closed, if todo goes out of scope without the
//following line, the file won't be reopenable until the OS deals with this
//resource leak:
fclose( todo );
}
By shifting the burden onto the machine, we reduce debugging and testing costs. Further, RAII can help with writing costs, by reducing otherwise complex error-handling code, which can get hellishly complex in the presence of C++ exceptions without RAII. With RAII, the exception mechanism simply unwinds the stack, calling the relevant destructors which clean everything up auomatically. This next comparison uses std::auto_ptr, the destructor of which simply calls delete on the underlying pointer when it is destroyed:
//C++ style (exception safe) RAII:
void example3() {
std::auto_ptr< A > a = new A( 42 );
std::auto_ptr< B > b = new B;
//if the allocation fails (new throws std::bad_alloc) or...
//if B's constructor fails (throws an exception)...
//a's destructor will automatically delete the object created with "new A( 42 )".
a->use();
b->do_something( 3.1415 );
//...and we're already done! a/b's destructor will clean up house after us.
}
//Exception safety without RAII:
void example4() {
A * a = 0;
B * b = 0;
try { //may incurr a slight extra overhead on some compilers
a = new A( 42 );
b = new B;
a->use();
b->do_something( 3.1415 );
} catch( ... ) { //need to catch just to rethrow later
delete b; //taking advantage of this being a null op if a == 0
delete a;
throw; //rethrow back up the stack
}
delete b; //duplicated code (unless we want to pay the expense of throwing)
delete a;
}
//Avoiding exceptions (and exception unsafe):
void example5() {
A * a = new (std::nothrow) A(42); //hope A's ctor dosn't throw
//(nothrow only stops new from throwing std::bad_alloc)
if ( a ) { //....tower of ifs of doom!
B * b = new (std::nothrow) B; //hope B's ctor dosn't throw
if ( b ) { //....for every resource!
a->use(); //hope A::use dosn't throw
b->do_something( 3.1415 ); //hope B::do_something dosn't throw
delete b;
}
delete a;
}
}
Implementing RAII
Implementing RAII in C++ is pretty simple. The key bits are copy constructors, assignment operators, and destructors.
The first two simply need to be set up in a manner soas to either allow the resource to be shared between multiple objects (to avoid one freeing the resource before the other is done with it), clone the shared resource (so each has their own copy), or to disable the operation entirely (by making them private and not implementing them). The destructor, obviously, simply needs to clean up any resources "owned" by the class. std::ofstream's bits might look something like:
class ofstream {
FILE * file;
ofstream( const ofstream& ); //disable copying
ofstream & operator=( const ofstream& ); //disable copying
public:
ofstream() {
file = NULL;
}
ofstream( const char * filename ) {
file = NULL;
open( filename );
}
~ofstream() {
close();
}
void open ( const char * filename ) {
if ( file ) fclose( file );
file = fopen( filename , "w" );
}
void close() {
if ( file ) fclose( file );
file = NULL;
}
};
RAII in .NET
Like many .NET languages, the undeterministic timing of exactly when an object is destroyed (when the garbage collector gets around to it, rather than when the programmer manually specifies) makes RAII a bit more of a challange — and also less necessary, without the need to manually track every memory allocation. The equivilant to (unmanaged) C++'s destructors, finalizers, are often discouraged due to the tricks it plays on garbage collection mechanisms. There is an alternative though — classes can implement the IDisposable pattern, with Dispose taking over the role of a finalizer for the task. The using keyword can be used to automatically call Dispose() at the end of a block, in the same way (unmanaged) C++'s destructors would automatically:
//In C#:
void Example()
{
using (FileStream todo = File.OpenWrite("todo.txt"))
{
todo.WriteLine( "Buy some milk" );
}
//todo.txt is automatically closed when the todo has Dispose() called
//(by the end of the using block)
}
Faking RAII in Ruby
Ruby's lack of destructors or similar mechanism means RAII is not implementable in the language (at least as far as automatic deallocation goes). That said, it's flexible blocks allows it to mimic .NET's using statements almost directly. The common pattern is to pass a resource loading function a block, which will then be executed (with the resource in question passed as an argument), then closed. The Ruby equivilant of the todo.txt example:
def example()
File.open( "todo.txt" , "w" ) do | file |
file.puts "Buy some milk"
end
#.open automatically .closes the file after the block has been run.
end
What File.open's source could look like:
class File
def File.open( filename , flags )
file = File.new( filename , flags )
if block_given?
yield file #call the attached block with argument file
file.close()
return nil
else
return file #will need to be cleaned up by the caller
end
end
end