Throwing exceptions

The syntax to throw exceptions might seem at first simple but, as the other bits and pieces of exception-related syntax, it has numerous subtle points.

The throw keyword used to throw exceptions actually has 3 different uses:

Interestingly, the seemingly simple syntax used to throw an exception is a bit more complicated than it looks. The exact definition of this syntax is: throw assignment-expression [1] . The static type of the assignment-expression is then used to initialize a temporary object of that type (through the type's copy-constructor) into a magic memory location which must stay valid during the subsequent unwinding. The magic memory location used is, of course, completely implementation-dependent. The temporary object is destroyed (that is, its destructor is first invoked and then its magic memory location is freed) when the stack unwinding completes.

The C++ specification suggests that the parameter to the throw statement can be viewed as an argument to a function call named throw. The syntax described above can thus be considered equivalent to throw (assignment-expression); which means that it is possible to replace the assignment-expression with anything used in the parameters to a normal function call. Most notably, the following sample code shows a few interesting examples:

void function (void) {
        // void bar (Foo foo);
        Foo foo = Foo ();
        bar (foo);
}
void function (void) {
        // void bar (Foo *foo);
        Foo *foo = new Foo ();
        bar (foo);
}
void function (void) {
        // void bar (Foo foo);
        bar (Foo ());
}
void function (void) {
        // void bar (Foo *foo);
        bar (new Foo ());
}

All these examples can be re-written with throw. They are commented below.

void function (void) {
        // push a copy of the foo object on the stack and invoke throw.
        Foo foo = Foo ();
        throw foo; // 1
}
void function (void) {
        // push a copy of the foo pointer on the stack and invoke throw.
        Foo *foo = new Foo ();
        throw foo; // 2
}
void function (void) {
        // allocate the foo object on the stack, constructs it and invoke throw.
        throw Foo (); // 3
}
void function (void) {
        // call new to allocate memory, construct this memory, push a copy
        // of the pointer returned by new on the stack, invoke throw.
        throw new Foo (); // 4
}

  1. The copy of the object constructed on the stack for Foo is used to copy-construct the magic temporary copy which is destroyed as soon as stack unwinding completes. It is interesting to note that the original object on function's stack is destroyed before its copy since the stack unwinding code invokes its destructor with the destructor of all the local variables of the function.

  2. A copy of the pointer to the object allocated by the operator new is pushed on the stack. The C++ runtime allocates a magic temporary copy of the pointer and deletes this copy o fthe pointer when stack unwinding completes. Here, no-one is reponsible for invoking operator delete on the object's object. As such, the final catch block which receives this exception should delete the pointer to the exception to avoid a memory leak.

  3. An object is allocated on the stack and is constructed there. The C++ runtime then uses this object to copy-construct the magic temporary copy. The original object is destroyed during stack unwinding as in 1).

  4. The memory for an object is allocated by operator new, its constructor is invoked, a pointer pointing to this object is pushed on the stack and the C++ runtime creates a magic temporary copy of the pointer. Here, as in 2), someone must decide to invoke operator delete on the object created for the throw. Most likely this will be the code in the final matching catch block.

To summarize, it is important to understand that the exception object received by each matching catch block is a copy of the original exception object, created in the throw statement. It is thus vital that both its copy constructor and its destructor (to destroy the copies) are available to the program. The GNU C++ compiler will issue a warning if this not the case. It is also vital to understand that if you use a pointer as the exception object, the problem of freeing the memory it points it must be solved.

Given all these constraints, I'd suggest to always throw a class type (don't try to throw things such as int or const char *) and to never throw a pointer to such a class type. This will ensure that the memory used by exception objects will be automatically managed by the C++ compiler and its runtime, thus reducing potential bugs.



[1] See section 15-1, paragraph 1 in the C++ standard.