Reliable Software Logo
 Home  >  C++ Resources  > Resource Management   >  Resources and their Ownership

Strong Pointers and Resource Management in C++

Resources and their Ownership

My favorite definition of a resource is "anything that your program has to acquire and then release." Memory is of course the prominent example of a resource. It is acquired using new and released using delete. But there are many other types of resources--file handles, critical sections, GDI resources in Windows, etc. It is convenient to generalize the notion of a resource to encompass all objects created and released in a program, the ones allocated on the heap as well as the ones declared on the stack or in the global scope.

The owner of a given resource is an object or a piece of code that is responsible for its release. Ownership falls into two classes--automatic and explicit. An object is owned automatically if its release is guaranteed by the mechanisms of the language. For instance, an object that is embedded inside another object is guaranteed to be destroyed when the outer object is destroyed. The outer object is thus considered the owner of the embedded object.

Similarly, every object that is declared on the stack (as an automatic variable) is guaranteed to be released (destroyed) when the flow of control leaves the scope in which it is defined. In this case, the scope itself is considered the owner of the object. Notice that automatic ownership is compatible with all other mechanisms of the language, including exceptions. It doesn't matter how you exit a scope, be it normal flow of control, a break statement, a return, a goto, or a throw--automatic resources are always cleaned up.

So far so good! The problem however starts with pointers, handles and abstract states. If access to a resource is through a pointer, as is the case with objects allocated on the heap, C++ doesn't automatically take care of its release. Instead, the programmer has to explicitly release the resource using the appropriate programming construct. For instance, if the object in question was created by calling new, it should be deallocated by calling delete. A file that was opened using CreateFile (Win32 API) should be closed using CloseHandle. A critical section that's been entered using EnterCriticalSection should be exited using LeaveCriticalSection, etc. A "naked" pointer, file handle or a state of a critical section has no owner that would guarantee its eventual release. The basic premise of Resource Management is to make sure that every resource has its owner.

The First Rule of Acquisition

A pointer, a handle, a state of a critical section will have its owner only if we encapsulate it in objects that follow the First Rule of Acquisition:
Allocate resources in constructors and release them in corresponding destructors.

If you think about it for a moment you'll realize that, once you encapsulate all resources according to rule one, you are guaranteed not to have any resource leaks in your program. This is pretty obvious if you only consider those encapsulating objects that are allocated on the stack or are embedded inside other objects. But what about those that are allocated dynamically? Not to worry! Anything that is allocated dynamically is considered a resource and, consequently, will also have to be encapsulated according to rule one. This chain of objects encapsulating objects encapsulating resources has to end somewhere. It ends with the top-level owners who are either automatic or static. These are guaranteed to be released on exiting the scope, or the program, respectively.

Here's a classic example of resource encapsulation. In a multithreaded application the problem of sharing an object between threads is usually solved by associating a critical section with such an object. Every client who wants to access this shared resource has to first acquire the critical section. For instance, this is how a critical section might be implemented in Win32.

class CritSect
{
    friend class Lock;
public:
    CritSect () { InitializeCriticalSection (&_critSection); }
    ~CritSect () { DeleteCriticalSection (&_critSection); }
private
    void Acquire () 
    { 
        EnterCriticalSection (&_critSection);
    }
    void Release () 
    { 
        LeaveCriticalSection (&_critSection);
    }

    CRITICAL_SECTION _critSection;
};

The tricky part is that we have to make sure that each client who enters the critical section must also exit it. The "entered" state of a critical section is therefore a resource and should be encapsulated. The encapsulator is traditionally called a lock

class Lock 
{
public:
    Lock (CritSect& critSect) 
        : _critSect (critSect) 
    {
        _critSect.Acquire ();
    }
    ~Lock ()
    {
        _critSect.Release ();
    }
private
    CritSect & _critSect;
};

Locks are used in the following manner.

void Shared::Act () throw (char *)
{
    Lock lock (_critSect);
    // perform action -- may throw
    // automatic destructor of lock
}

Notice that no matter what happens, the critical section is guaranteed to be released by the mechanisms of the language.

There is one important thing to remember--each resource has to be encapsulated separately. That's because resource allocation is almost always a failure-prone operation, if only by the fact that there is always a finite supply of any given resource. We will assume that a failed resource allocation might result in an exception--in fact, very often this is exactly what should happen. So if you are trying to kill two birds with one stone, or allocate two resources in one constructor, you might get into trouble. Just think what happens when the first allocation succeeds and the second one throws. Since the construction hasn't been completed, the destructor won't be called, and the first resource will leak out.

This situation can be easily avoided. Whenever you have a class that requires more than one resource, write small encapsulators for them and embed them in your class. Every fully constructed embedding is guaranteed to be deleted, even if the construction of the embedding object doesn't complete.


NextNext: Smart Pointers