RAII in C++

RAII is an acronym which means "Resource Acquisition Is Initialization". And what's it about? It'a low level design pattern, what some call "idiom". Its aim is to ease and ensure resource deallocation. Resources include file handles, bitmaps, data base connections, heap memory, etc.

RAII was created by Bjarne Stroustrup, the face of the team which created C++. Even though RAII was born in C++, it's so general that it can be used in any object oriented language with an exception handling mechanism. It can even be implemented in languages which do not meet these requirements, but it is not so trivial and might not be worth the hassle.

The idea is quite simple: allocate the resource ONLY in the constructor and deallocate it ONLY in the destructor. Hence the name RAII: When we initialize the object (call the constructor), we acquire the resource. Initialization is acquisition. Acquisition is Initialization. This way, the resource's usage is tied to the object's lifetime, which simplifies things since we must deal with only one lifetime instead of two.

Aside from simplifying code, RAII makes it more robust.In most OO languages with exception handling, it is guaranteed that whenever an exception ocurrs inside a method, the destructors for all objects local to that method are invoked before unwinding the call stack. Therefore, if all local resources were acquired using RAII, they will all be deallocated even if an exception is thrown. So if we use RAII consistently, we can save ourserlves a few try-catch blocks.

Let's see some code which uses resources without RAII:

void foo() {
    try{
        CFile logfile;
        logfile.Open("logfile.txt");
        logfile.Write("hello logfile!");
        // Keep using logfile ...
        logfile.Close()
    catch(std::exception){
        logfile.Close();
    }
}

This code has a few issues:

  • close() must be explicitly invoked. Each time we write code which uses CFile, we must remember to call close() when we are done.
  • close() si invoked inside the try and the catch block. This is due to a limitation of C++: there is no finally clause. So we have code duplication, which increases chances of forgetting to call close().
  • And the worst: If an exception which is not derived from std::exception is thrown inside foo(), logfile is not closed. If catching std::exception is a bad idea, going more general ( catch(...) ) is even worse.

In order to apply RAII in this example, we must make private CFile's constructor and the open() and close() members. Then, we define a constructor which receives the file name and uses it to call open(). Then, have the destructor call close(). Once we're done refactoring CFile, we go for foo:

void foo() {

    CFile logfile( "logfile.txt" );
    logfile.Write("hello logfile!");
    // Keep using logfile ...
    // close() is invoked by ~CFile, when logfile goes out of scope, or if an exception is thrown
}

I removed the try-catch block based on the assumption that it was there only to catch exceptions related to the use of logfile. Since we only caught those in order to close logfile, now that is closed automatically on every scenario, we don't need the try-catch block.

Comments

Popular posts from this blog

VB.NET: Raise base class events from a derived class

Apache Kafka - I - High level architecture and concepts

Upgrading Lodash from 3.x to 4.x