What is RAII?
RAII stands for “Resource Acquisition Is Initialization”. It is probably one of the nicest and most useful features that C++ has given to the world. D and Rust incorporated it as part of their specifications too.
What does it mean?
RAII, “obviously”, means that any entity that asks for a resource from the system (memory, file handle, network connection, etc.) should be responsible for releasing such a resource when its life has ended.
In C++ jargon, it means that any resource needed by an object must be acquired by the object’s constructor and released in its destructor.
Thanks to this very interesting feature, when a variable that represents an object created with value semantics goes out of scope, its destructor is invoked automatically and seamlessly, thus releasing any resource the object could have acquired in its lifetime.
That solves a lot of resource-related issues in a very transparent way when the variables go out of scope:
- Any dynamically allocated memory owned by this object can be released.
- Any file handle open can be closed.
- Any network connection can be closed.
- Any database connection can be closed.
- Any Registry handled returned.
- Any mutex unlocked
- … and so on.
The nicest thing is, though several languages provide garbage collectors that are limited to handling memory, RAII is a cleaner alternative to handling NOT ONLY memory but any kind of resources.
Let’s see how we can use it.
First, let’s see how these constructor and destructor are invoked:
#include <iostream>
class A final
{
int n;
public:
explicit A(int n) : n{n} { std::cout << "Hello " << n << std::endl; }
~A() { std::cout << "Bye " << n << std::endl; }
};
void test()
{
A a{1};
A b{2};
A c{3};
}
int main()
{
std::cout << "Begin" << std::endl;
test();
std::cout << "End" << std::endl;
}
When running this, the output will be:
Begin
Hello 1
Hello 2
Hello 3
Bye 3
Bye 2
Bye 1
End
We can notice two things here:
- The destructors are invoked automatically before exiting the function test. Why there? Because a, b, and c were created in that code block.
- The destructor calling order is inverse to its creation order.
So, since the destructors are invoked automatically, we can use that interesting feature (RAII) to free any resource acquired by our code. For example, modifying the class A
to store that int value in the heap instead (bad idea, by the way):
class A final
{
int* pn;
public:
explicit A(int n)
: pn{new int{n}}
{
std::cout << "Hello " << *pn << std::endl;
}
~A()
{
std::cout << "Bye " << *pn << std::endl;
delete pn;
}
};
Notice that I am acquiring the resource (allocating memory) in the constructor and releasing it in the destructor.
In this way, the user of my class A does not need to worry about the resources it uses.
“Out of scope” also means that if my function ends abruptly or returns prematurely, the compiler will guarantee that the destructor of the objects will still be invoked before transferring the control to the caller.
Let’s test that by adding an exception:
#include <iostream>
class A final
{
int* pn;
public:
explicit A(int n)
: pn{new int{n}}
{
std::cout << "Hello " << *pn << std::endl;
}
~A()
{
std::cout << "Bye " << *pn << std::endl;
delete pn;
}
};
void test(int nonzero)
{
A a{1};
A b{2};
if (nonzero == 0)
throw "Arg cannot be zero";
A c{3};
}
int main()
{
std::cout << "Begin" << std::endl;
try
{
test(0);
}
catch (const char* e)
{
std::cout << e << std::endl;
}
std::cout << "End" << std::endl;
}
Notice that I am throwing an exception after objects a
and b
were created. When the exception occurs, the function test ends abruptly, but it will invoke the destructors of a
and b
before going to the catch block.
The destructor of object c
was not invoked because the object was not created when the exception occurred.
The same behavior occurs if you return prematurely from a function.
Now, look at the class B
that I have added to my example:
#include <iostream>
class A final
{
int* pn;
public:
explicit A(int n)
: pn{new int{n}}
{
std::cout << "Hello " << *pn << std::endl;
}
~A()
{
std::cout << "Bye " << *pn << std::endl;
delete pn;
}
};
class B final
{
A a;
A b;
public:
B(int valueA, int valueB) : a{valueA}, b{valueB} { }
};
void test()
{
B a { 4, 5};
B b { 6, 7};
}
int main()
{
std::cout << "Begin" << std::endl;
test();
std::cout << "End" << std::endl;
}
The output is:
Begin
Hello 4
Hello 5
Hello 6
Hello 7
Bye 7
Bye 6
Bye 5
Bye 4
End
Why are the destructors of A being called when B objects go out of scope if I did not write a destructor for B?
Because when you do not write a destructor, the compiler generates one automatically that invokes the destructors of all member variables with value semantics.
So, if your basic classes handle resources explicitly, the likelihood of you needing to acquire or release resources explicitly in your constructors or destructors is actually low.
What about pointers?
RAII does not work with raw pointers, so if you declare something like:
int* array = new int[1024];
in a function, nothing will happen when that variable array goes out of scope.
Is there any way to have pointers handled by RAII?
YES! Through smart pointers!
Other non-memory related uses?
std::ifstream
andstd::ofstream
close automatically the file they opened to be read or written.std::lock_guard<T>
locks a mutex in its constructor and unlocks it in its destructor, avoiding threads locked by mistake.- If you are writing some UI, you probably could need a
MouseRestorer
that would automatically set the mouse to its default value after being changed to an hourglass in a time-consuming piece of code