RAII: Resource Acquisition Is Initialization
Resource Acquisition is Initialization (RAII) is a programming pattern, which binds the life time of a resource to the lifetime of the holding object. The resource is acquired during the initialization of the object, typically in a constructor and released during its destruction, typically in a destructor. The resources themselves can be anything like heap memory, file descriptors, sockets, locks, database connections, etc. Although this idiom was popularised by C++, other languages/frameworks have borrowed from it. For example,Context Managers in Python provide the same exact functionality described above.
The main reason to use RAII is to guarantee the release of the held resource, irrespective of the flow of your program. Since the lifetime of the resource is tied to the lifetime of your object, once your objects goes out of scope, whether by normal exit or by an exception, the resource is released. This prevents us from checking all possible paths of the program and manually releasing the resource in each of the code paths. Let us consider the below piece of code,
Here, we are trying to read contiguous blocks using multiple read()
system calls. We can see that we need to call close()
on all possible paths for us to not leak the file descriptor. RAII is well tailored for such situations. We can use a RAII wrapper for the file descriptor as follows,
And now, we can read/write as below,
As you can see above, we can be sure that the file descriptor will be released when the object goes out of scope. Even in the presence of exceptions or any errors, the destructor of the RAII wrapper is going to be invoked and the resource released due to stack unwinding.
Using RAII, the resource is maintained as a class invariant and the methods of the class can use it without performing any other runtime checks. A RAII wrapper class can throw an exception from its constructor if the resource cannot be acquired, which the client can then catch and process. And like all C++ destructors, the RAII wrapper class cannot throw during destruction. The resources and others are destroyed in the reverse order of their initialization and an object is destroyed only if it was fully constructed.
Other benefits provided by RAII, apart from exception safety, include encapsulation and locality. We encapsulate the resource in a single class that is then used to use the underlying resource. RAII also helps locality such that the clients have a single place where the acquisition and destruction is being done and makes it clear to reason about.
RAII works only for stack allocated objects and when the object goes out of stack, the destructor gets called with the resource getting freed. Heap allocated objects need to manually manage the acquisition and freeing of the resources. In C++, this is again made easier by using the smart pointers. Here, the user allocated resource is encapsulated in the smart pointer and the destructor gets called when the pointer goes out of scope. The smart pointers are made more intuitive using move semantics and this helps the clients move around long lived objects, across scopes.
Let us use a smart pointer to work with a user allocated resource. Consider the code below,
UserRes *ur = new UserRes();
call_unsafe_func();
delete ur;
Here, if call_unsafe_func()
throws, the resource is leaked. What we need in this situation is a smart pointer that calls the destructor when the stack is unwound. This can be done like,
std::unique_ptr<UserRes> p1 = std::make_unique<UserRes>();
call_unsafe_func();
Here, even if the call_unsafe_func()
throws, the destructor is called when the stack is unwound. Also, you can explicitly move the resource around using std::move()
like,
std::unique_ptr<UserRes> another = std::move(ur);
The C++ classes like std::vector
, std::string
, std::list
and many other standard library classes use RAII to manage resources and does not require an explicit cleanup. The thing to note is that, sometimes a program crashes and does not get a chance to unwind the stack and clean up resources. This behaviour, however, does not let the held resources leak since the OS cleans up when the program exits, either normally or abnormally. Hence, all execution paths are covered and the resources freed.
This idiom is also called as Scope Bound Resource Management and in Python, we will enclose the resource to the underlying scope and that said resource will be freed when the execution exits the scope. The code in Python will look something like,
with open('some_file', 'r') as f:
for line in f:
print line
Here, the close()
method is called when the scope is exited and resources cleaned. In Java, the finally{}
block is where you clean up in the case of a normal workflow or otherwise.
That’s it. For any discussion, tweet here.
https://www.artima.com/intv/modern3.html https://www.informit.com/articles/article.aspx?p=30642&seqNum=8