Contributor's Corner

A collection of hopefully helpful information
Functionality Clarity Elegance

  C++ Exceptions   

Concepts & Techniques

Rule Two:
Use features of the language to help

The most helpful language feature is the fact that all locally constructed objects will be destroyed when a function returns (or a block - delimited by {} - completes) whether an exception is thrown or not. This means that, if an exception is thrown from deep within a set of function calls, the destructors will be called for every object created within those functions. This allows the use of RAII (Resource Acquisition Is Initialisation) techniques. Essentially, this means that resources used by an object (memory, file handles, mutexes, etc) are acquired by constructors, and cleaned up by destructors. As the destructors will be called as control passes from a throw statement to the first matching catch clause, this means that all resources grabbed in between will be cleaned up. While member functions may be used to reinitialise the resource (eg a class working with an array may dynamically resize the array), those functions must ensure that they do not cause a leak, and ensure that the destructor will have something valid to clean up. The language also guarantees that, if an exception is thrown while an object is being constructed, that any parts of the objects successfully constructed will be destroyed. This can be used to avoid resource leaks. For example;
 
class Cat {};   // definition of Cat is incidental to this example
 
class Base
 
{
 
    public:
 
        Base() : x(new Cat) {};
 
        virtual ~Base() {delete x;};   
 
    private:
 
        Cat *x;
 
};
 

 
class Dog {};
 

 
class Derived : public Base
 
{
 
    public:
 
       Derived(): Base(), y(new Dog) {throw Foo;};
 
       ~Derived() {delete y;}
 
    private:
 
       Dog *y;
 
}
 
The language guarantees that, when creating a Derived, the base classes are constructed first, and then data members are constructed. In this example, Base is constructed, then y is initialised. The constructor of Derived throws an exception. The language then guarantees that the components of Derived that have been successfully initialied will be cleaned up, in reverse order of their construction. So, in this example, y will be deleted, and the destructor of Base will be invoked. This approach means that, if construction of an object fails, the object never exists (and an exception is encountered).
This rule allows one to avoid the common, but naive, "two phase construction" approach, which essentially means that a constructor sets the object into some default "safe state", and then requires calling of something like an init() function to initialise the object into the needed state. The problems with such an approach include the possibility of forgetting to call the init() function, the possibility of calling it more than, and (often) the need for every member function to check that the object is not in the "default" state before using it. These problems increase likelihood of errors, and make the class implementation more difficult to understand and maintain. The most usual reason for "two phase construction" is avoiding grabbing resources before they are needed. This can also be avoided by not constructing the whole object until it is needed. This practice also simplifies the provision of multiple functions by one class. In this example, class Base manages an instance of a Cat, so the implementer of class Derived need not worry about the creation and destruction of that Cat.

 Concepts & Techniques: Rule OneConcepts & Techniques: Rule Three