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:
In the context of a block of code marked by a catch statement, throw; is used to specify that the exception caught by the block is to be rethrown. This means that instead of stopping exception propagation at this catch statement, the C++ runtime will keep on unwinding the stack with the exception thrown originally. In any other context, invoking throw; is considered an error and is caught by the runtime.
In the context of a function or method declaration, ret_type function (par_type1 ,par_type2) throw (Exception1Name, Excepion2Name); specifies explicitely a subset of the exceptions the method can throw. The function can throw any other exception. The exception list specification is thus nothing more than informative. A special case of this syntax is when the exception list is empty ret_type function (par_type1 ,par_type2) throw ();: this specifies that the function cannot throw _any_ exception. It should be noted that such a promise is extremly difficult to hold and that extremly few functions specify this. You are thus recommended to think twice about adding this into your APIs: the compiler is free to use this information to generate code or optimize it...
Finally, in the context of any code block, throw exception_object; throws an exception and triggers its propagation up into the stack.
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
}
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.
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.
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).
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.