Resource Acquisition is Initialisation (RAII)
During the past 3 years, I used C++ as the principal programming language after a long break during which I was doing most of my development on C# inside Unity. Also, the large part of my C++ work happened before modern C++ became a thing and for that reason, my first impact with it was a blast!
This post is about RAII (Resource Acquisition Is Initialization), an important C++ programming technique to properly handle resource lifetime. The technique relies on a feature of the language that guarantees to call the destructor of objects allocated on the stack once they go out of scope. Consequently, the correct implementation of RAII requires to acquire and initialize the resource in the constructor of the object and to relinquish the resource in the destructor of the object.
Let’s try this with an example.
class FileReader {
public:
FileReader(const char* filename) {
mFile.open(filename);
}
~FileReader() {
mFile.close();
}
std::string read_line() {
return file.readLine();
}
private:
File mFile;
}
There is a last key element: object instances must be allocated on the stack and not on the heap, the following example clearly show this last point.
std::string readFirstLine_Stack(const char* filename) {
FileReader file(filename);
return file.readLine();
// File closed here because file goes out of scope and destructor is called.
}
std::string readFirstLine_Heap(const char* filename) {
FileReader* file = new FileReader(filename);
return file->readLine();
// Destructor never called because file is never deleted.
}
void main() {
readFirstLine_Stack("file.txt");
readFirstLine_Heap("file.txt");
}
Smart Pointers are a typical example of RAII. Managing memory lifetime and ownership are the most challenging parts of C++: forget to call delete on a pointer, can cause a memory leak and call delete more than once can lead to undefined behaviors. A Smart Pointer is a class that wraps a raw pointer and takes care of release the allocated memory when it goes out of scope. By implementing RAII, Smart Pointers relieve us from the troubles of manage memory.
RAII is widely used across the C++ Standard Library: the majority of the core classes manage their resources using RAII: std::string
, std::vector
, std::thread
and many others acquire their resources in constructors and release them in their destructors.
It’s worth to mention that this technique goes under another name: Scope-Bound Resource Management (SBRM). As an example of “scope-bound” implementation of RAII, let’s look at the std::scoped_lock
:
namespace std
{
template <typename Mutex>
class scoped_lock<Mutex> {
public:
using mutex_type = Mutex;
explicit scoped_lock(Mutex& m) : m_(m) {
m.lock();
}
explicit scoped_lock(std::adopt_lock_t, Mutex& m) : m_(m) {}
~scoped_lock() {
m_.unlock();
}
scoped_lock(const scoped_lock&) = delete;
scoped_lock& operator=(const scoped_lock&) = delete;
private:
Mutex& m_;
};
} // mamespace std