Wednesday, 23 October 2013

Design for errors #2: RAII

RAII stands for Resource Acquisition Is Initialization. This is another simple technique that prevents a lot of errors in code.

Imagine that you have to read some content from a file. You can create code like this:

void readFile(char *fileName)
{
  FILE *f = fopen(fileName, "r");
  // some code
  fclose(f);
}

Note: error handling was removed from all code samples for simplicity.

This looks fairly good on first sight: opened file is closed at the end of function, so no file descriptor is leaked. But what if some code part contains branches and returns early in some of them? In that case explicit fclose() call must be added before each return. Other option is to enforce single exit point policy for whole code. But that is a bad coding practice and can lead to code with unusually large indentations.

RAII idiom helps in such situations. Minimal implementation of RAII for FILE object could look like this:

class FileHandle
{
public:
  FileHandle(char *fileName) : m_f(fopen(fileName, "r")) {}
  ~FileHandle() { fclose(m_f); }
  FILE * file() const { return m_f; }
private:
  FILE *m_f;
};

void readFile(char *fileName)
{
  FileHandle handle(fileName);
  // some code
}

This is simple class that handles all aspects of FILE initialization and destruction - it opens file in constructor and closes it in destructor. That is everything required to implement RAII idiom. What are the benefits?

Object handle exists only in scope of function readFile  When the scope is leaved (no matter how and where) handle is destroyed. This means that destructor is called and fclose(f) is executed. There is no need to add any code before each return and at the very end of function. Compiler takes care of it. In addition this version is exception safe - object's destruction will take place if an exception is thrown inside some code block.

Another plus of this is that we have to write at least 2x less code for file management - one line instead of two in case of just one exit point. Less code means less possible errors.

Such RAII classes should be used for everything that has state that needs to be restored, like:
Qt lack at least one of such classes. QObject defines blockSignals(bool) method that make a perfect pair (with true and false parameter) for a simple RAII class:

class QObjectSignalBlocker
{
public:
  QObjectSignalBlocker(QObject *o)
    : m_o(o),
      m_block(o->blockSignals(true)) {}
  ~QObjectSignalBlocker() { m_o->blockSignals(m_block); }
private:
  QObject *m_o;
  bool m_block;
};

This one block all signals from given QObject and stores previous value of signalsBlocked() status. At the end of scope this value is restored.

Within Kadu we use similar method to block change notifications of our business objects. Each of it has a dedicated ChangeNotifier (.h, .cpp) instance. On each change of property value a notify() method is called on this object and all interested parties are informed by changed() signal. However, if a bulk change is made (several properties are modified at once), there is no need to emit changed() each time. So a pair of block() and unblock() method is provided to enable buffering notifications and merging them into one. Lately I've realized that this is perfect candidate for RAII class, so ChangeNotifierLock (.h, .cpp) was born. It can be set to one of two modes: fire notification or forget all changes that was made during it existence.

Changing code to use it was really simple. We do not have many uses of ChangeNotifier. Also there was no single misuse of this (all block() calls were properly paired with unblock()), but in two cases distance between the calls was bigger than one screen of code. This could lead in future in unfortunate omissions and GUI breakdown (as nothing would get updated anymore). Using RAII class saved us a few lines of code and removed possibility of introducing one kind of error into our code.

From now I'll look for such cases more carefully and implement this idiom where applicable.

1 comment:

  1. Instead of FileHandle one can also use QSharedPointer, using fclose as the "deleter" function

    ReplyDelete