The importance of knowing RAII.

What is RAII?

Resource acquisition is initialization (RAII) is a technique for manage resources memory, files, sockets, etc. This method is used for many programming languages as D, Ada, Vala, and Rust.

RAII makes clear which is the life-time of all variables and objects on our code. For example, if we have:

int main() {
  int foo;
  bar();
  baz();
  // More stuff ...
}

We know that the variable foo will be created (and also all resources needed to do this) on line 2. And all the resources will be released on line 6.

Once a variable reach the end of scope will be deleted. If instead of a variable, is a object, when it reach the end of scope the destructor will be called.

It is based on the fact that destructors can perform any cleanup code as soon as a variable falls out of scope. This code is guaranteed to be executed, whether the function is left by a return value or an exception, and the execution takes place automatically.

class MyClass {
public:
  MyClass(int n) : num(n){
    ptr = new char [10];
  }
  ~MyClass() {
    delete []ptr;
  }
private:
  int num;
  char *ptr;
};

int main() {
  MyClass obj(2);
  // bar();
  // baz();
  // More stuff ...
}

Consider that this code is just for a demonstration, I don't recommend this use of char on C++. Use std::string instead for this purposes.

In this case, we know that MyClass is created on line 15 calling its constructor, creating the num variable and then allocating 10 bytes of memory on the heap for ptr. When the code reach the line 19 the destructor of MyClass is called, as we have memory allocated on the heap, we must free it manually. This is made using the delete operator.

Taking advantage of RAII make safer code.

Now that we know how is the life-time of the objects, we can take advantage of this to free up resources automatically when the program reach the end of the scope, instead of use raw pointers and free it manually (possibly generating memory leaks if we forget something).

To represent this behaviour I'll be using a very simple example:

Imagine you are creating a simple program that reads its configuration from
a text file and loads it into a string for parse it later.

If we think about the logic behind this program, we can think that the structure will be:

#include <cstdio>
#include <iostream>
using namespace std;
int main() {
  // Program begin
  string conf;
  FILE *myfile;
  int ch;

  myfile = fopen("conf.txt", "r");

  if (myfile != NULL) {
    while ((ch = fgetc(myfile)) != EOF) {
      conf += ch;
    }

  } else {
    cout << "Unable to open file";
  }
  do {
    /*
      All the program logic.
    */
  } while (working);
  // End
  cout << conf;

}

Simple and useful program, but if we pay attention the file descriptor myfile is never closed. All the program is running with this file open, and if this file is really big, this is a huge problem.

On this case we can do two thinks:

  • The first one is the logic one insert a fclose(myfile); on line 16 or later. This is the best practice in this case.
  • Or we could use the C++ version of file stream.

    #include <fstream>
    #include <iostream>
    using namespace std;
    int main() {
      // Program begin
      string conf;
    
      string line;
      ifstream myfile("conf.txt");
      if (myfile.is_open()) {
        while (getline(myfile, line)) {
          conf += line;
        }
    
      } else {
        cout << "Unable to open file";
      }
      do {
        /*
          All the program logic.
        */
      } while (working);
      // End
    }
    

    In this version there is no chance of memory leak, beacause when the code reach the line 24 myfile is closed automatically on its destructor. If we want to close the file before this point we could close it manually adding myfile.close() on line 14.