Saturday, May 19, 2007

The joys of RAII

A topic which, as Michael Caine might put it, "Not a lot of people know that". Alas.

One idiom which is almost unique to C++ amongst the languages in common use (for practical purposes this is defined as 'C' and its descendants -- C++, Java, C#; though it equally applies to Ruby or Python) is the concept of Resource Aquisition Is Initialisation (RAII). That is to say, when a resource -- whatever its type -- is acquired, it should be part of the initialisation of an object. Then, using the stack-based scope for variables, we can make the corresponding release operation happen naturally on exiting that scope, even during the stack unwinding following an exception*.

Managed languages (all those named above other than 'C') have separated out heap memory as a special (albeit frequent) case of resource allocation, handed the problem of tidying up to a garbage collector running inside the VM on which the code executes, and makes the deallocation of heap objects a non-deterministic process. For other types of resource, there are special idioms -- like IDisposable and the using construct in C# (or, if all else fails, try/finally).

In C++ all resources are treated equally, and all have deterministic points where clean-up can happen -- at the end of a {} delimited scope.

  • Need to release memory -- allocate it as a std::vector<> (for arrays) or std::auto_ptr<> (for a singly owned single object) or similar smart pointer in the appropriate scope
  • Need to release a COM object -- wrap it as a CComPtr<class T> template (ATL has a lot of these useful features kicking around for general Win32 native C++ programming). (Just be careful to release COM itself at the end of an enclosing scope, as destructors fire in arbitrary order).
  • Need to release some other resource (HANDLE, HINTERNET, GDI Object...) -- find (or, if you have to, write) another class to contain the resource and whose destructor frees it.

The classic is, of course, the MFC CWaitCursor -- create one at the start of a long-running block of code, and the cursor will show an hourglass over your application main window, until it is restored in the destructor.

If you are in a position of having to write native C++, then rather than considering it the equivalent of working in the Dark Ages of manual memory management, look to the power that the language offers you to solve the more general resource management problem at a single point.

*Related to this is the reason why destructors must never throw -- throwing during a stack unwinding leads to program termination, no saving throw; only the chance to to some last ditch tidying up.

No comments :