Skip to content
djewsbury edited this page Feb 4, 2015 · 2 revisions

#Resource Allocation Is Initialisation

Always follow RAII principles.

This is one of the most important rules. It's particularly critical because we use exceptions, but it's good practice either way.

This principle is usually called "RAII," which stands for Resource Allocation Is Initialisation. What this really means is:

  • all "resource allocation" operations should occur in a constructor of an object (and only in a constructor)

And it follows from this that the appropriate cleanup operations should happen in the destructor.

Here, resource allocation can mean many things:

  • allocating memory (ie, using operator new)
  • allocating an operation system resource from the OS
  • allocating a graphics API resource
  • opening a file or network connection
  • allocating from a fixed pool (of memory, or keys, or any resources)

Note that we're using a very broad definition for "resource allocation." It is not just allocating memory. It could include things like:

  • taking a reference to a reference counted object
  • locking a mutex
  • "begin"ing a profile event that requires a future "end"

A more general description could be to say this:

  • a "resource allocation" operation is any operation that requires cleanup

Also note that many resource allocation operations can fail, unexpectedly, at run-time (eg, out of memory, missing file, deadlock on mutex lock). This means that resource allocation operations tend to be the least reliable operations.

###Example

Consider the following code:

FILE* file = nullptr;
TRY {
  file = fopen("somefile", "rb");
  FunctionThatMayThrow(file);
  if (file) {
    fclose(file);
  }
} CATCH (...) {
  if (file) {
    fclose(file)
  }
  RETHROW;
} CATCH_END

The above code appears excessively crowded with cleanup code. We have to repeat the call to fclose in two places. This result is not only ugly, it's also unreliable. Changes to the function, or the cleanup procedure of FILE can easily create bugs.

Let's consider creating a class called BasicFile that will follow RAII rules. Here, the file object is our resource. We want to do the "allocation" step (ie, fopen) in a constructor, and the deallocation step in a destructor.

class BasicFile
{
public:
  FILE* GetUnderlying() { return _file; }

  BasicFile(const char filename[]);
  ~BasicFile();

  BasicFile(const BasicFile&) = delete;
  BasicFile& operator=(const BasicFile&) = delete;
private:
  FILE* _file;
};

BasicFile::BasicFile(const char filename[])
{
  _file = fopen(filename, "rb");
  // note, after calling fopen, we can't do anything
  // that might throw (see Exceptions And Constructors wiki page)
}

BasicFile::~BasicFile()
{
  if (_file) {
    fclose(_file);
  }
}

Now, we can re-write the original code as so:

BasicFile file("somefile");
FunctionThatMayThrow(file.GetUnderlying());

The new version is only 2 lines, but has the same behaviour as the original implementation. Not only is it cleaner, it will be much more reliable if any of the surrounding code changes.

This simple rule is critical for preventing leaks when exceptions occur. It's also just good style. Effective use of this rule can prevent many common coding errors.

... more detail coming ...