When we are programming, we often have to deal with resources. A resource is some system functionality of limited availability, that we have to manage explicitly. This means that before we use it we have to acquire it and when we are finished we have to release it.
Examples of resources are:
- Memory on the heap, we acquire memory on the heap with the
new
keyword and we must release it with thedelete
keyword. - Files, we acquire a file handle by asking the operating system to open the file, and we release it by asking for the file to be closed.
- Mutexes, we acquire a mutex by locking it and we release a mutex by unlocking it.
- Network or Database connections, these will have their own specific acquisition and release logic, and will usually require both.
So, let’s suppose we are using a file, and our code looks a little something like this:
public void DoSomeStuffWithAFile()
{
ofstream file("output-file.bin");
// do some stuff with this file
// do some other stuff
file.close();
}
Here, the file is our resource, we acquire it with the file
constructor and we release it with the close
method. However, suppose, somehow control returned from this method before we reached the final line: file.close();
. Then we will not have released this resource. This can cause pretty serious problems, as resources are by definition, limited in their availability. If we use up all the file handles like this, eventually we won’t be able to open any more files!
There are two ways control could return without us using the close
method. The first is that an exception is thrown somewhere between the creation of the file and the file.close()
call. When this happens, control will leave the DoSomeStuffWithAFile
and we will never close the file. If this exception is caught at a higher level, the file resource will be left open for the remaining duration of the program. It doesn’t really matter if an exception is thrown and not caught, because then the program will terminate and the operating system will release all the resources we held anyway.
The second is if we simply have a return statement somewhere in this method, something like:
public void DoSomeStuffWithAFile()
{
ofstream file("output-file.bin");
// do some stuff with this file
if(/* some condition */)
{
return;
}
// do some other stuff
file.close();
}
We should have a file.close()
call before this return statement, but we could forget it.
How can we avoid this two potential sources of resource leakage? Well there is a pattern that handles both! This pattern is called Resource Acquisition is Initialisation. When an object is created on the stack, it is guaranteed to be torn down when control leaves the current block, as the stack is unwound. This happens whether control leaves by normal control flow or because of a thrown exception. So, we encapsulate resource acquisition and release in a class. In our example we would do something like:
class FileHolder
{
public:
ofstream file;
FileHolder(string fileName) : file(filename)
{
}
~FileHolder()
{
file.close();
}
}
Then we use this FileHolder
class like so:
public void DoSomeStuffWithAFile()
{
FileHolder fileHolder("output-file.bin");
// do some stuff with this file
if(/* some condition */)
{
return;
}
// do some other stuff
}
Now, whenever control leaves the DoSomeStuffWithAFile
function, the destructor will be called on the fileHolder
object as the stack is unwound and the close
method on our file will be called.
We can use the same pattern with any resource, we create a holder class that acquires the resource in the constructor and releases it in the destructor. That way, we are using the unwinding of the stack to control the release of our resources, which I think, is pretty cool.
In this example we have left our file
object public. It would be better practice to make the internal file object private, and expose the methods we wish to use via methods on the FileHolder
class. That way we control exactly how the file is used, in particular, we won’t be able to do this:
public void DoSomeStuffWithAFile()
{
FileHolder fileHolder("output-file.bin");
// do some stuff with this file
fileHolder.file.close();
// do some other stuff
}
The above code may cause our file to be closed twice, once explicitly and once as the stack is unwound. This isn’t necessarily a problem for files, but we should avoid it. For other resources, releasing twice might be a serious problem.
This pattern has other advantages. Firstly the code for acquiring and releasing it are kept together logically. Which makes the resource management logic clearer and easier to maintain. Secondly it reduces code duplication as we do not need to explicitly acquire and release a resource every time we use it.
For some resources, RAII is implemented in C++ for us already. The two most common examples are lock_guard
which managers a mutex and smart pointers which manage memory.