Resource Acquisition Is Initialization
In the previous article we presented a simple timer class that initializes its internal clock in the constructor and computes the elapsed time in the destructor.
In fact, initializing a resource in a constructor and discarding it in a destructor is a common idiom in C++ called Resource Acquisition Is Initialization (RAII).
This is a great technique when dealing with exception handling: for example let's say at the beginning of a function you allocate 10 blocks of memory, open 2 files and acquire a lock. Of course you are really tidy so you properly free the allocated memory, close the files and release the lock at the end of the function.
However if an exception is raised in the middle of your code, you are in deep trouble: your program leaks memory, file descriptors are still opened and the lock is not released...
When leaving a block (even when an exception is raised), all variables allocated on the stack (i.e. local variables) are destroyed, this process is called stack unwinding.
Even if you are not concerned with exceptions, by using the RAII idiom you can write simpler code and there is no risk you accidentally forget to release a lock !
For example, using RAII with a lock:
// Before critical section { // Critical section ! Lock lock(mutex); // Construct Lock object and acquire mutex. // Do some super critical stuff ... // End of scope: variable lock is destroyed and the mutex is released automatically. } // After critical section |
auto_ptr
You can also use RAII to handle files or you can use it with raw pointers using the C++ std::auto_ptr class. When the variable reaches end of scope, the destructor automatically calls delete on the pointer. Using operator * we can manipulate an auto_ptr just like a pointer of the underlying type:
{ std::auto_ptr<int> p(new int); *p = 42; std::cout << *p << std::endl; // Allocated memory is released. } |
RAII in C
Using gcc variable attributes you can use the RAII idiom in C!
We use the cleanup attribute:
cleanup (cleanup_function)
The cleanup attribute runs a function when the variable goes out of scope. This attribute can only be applied to auto function scope variables; it may not be applied to parameters or variables with static storage duration. The function must take one parameter, a pointer to a type compatible with the variable. The return value of the function (if any) is ignored.
Wikipedia presents an example on how to automatically call fclose() on a file descriptor using the cleanup attribute:
#define RAII_VARIABLE(vartype,varname,initval,dtor) \ void _dtor_ ## varname (vartype * v) { dtor(*v); } \ vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval) void example_usage() { RAII_VARIABLE(FILE*, logfile, fopen("logfile.txt", "w+"), fclose); fputs("hello logfile!", logfile); } |