Given the code shown in the first section, it is possible to flatten the nested code shown in the first section:
// Initial code
class Error {};
class Automatic {};
class Foo
{
public:
void DoAction() throw(Error) { throw Error(); }
};
class Bar
{
public:
void DoAction() throw(Error) { throw Error(); }
};
void f()
{
Foo* foo = 0;
try {
Automatic a;
foo = new Foo;
foo->DoAction();
Bar* bar = 0;
try {
bar = new Bar;
bar->DoAction();
} catch (...) {
delete bar;
throw;
}
delete foo;
delete bar;
// a's destructor invoked automatically after leaving this scope
} catch (...) {
delete foo;
throw;
}
}
int main (int argc, char *argv[])
{
try {
f ();
} catch (Error &e) {
}
}
And the flattened version which looks much nicer:
// Flattened version.
class Error {};
class Automatic {};
class Foo
{
public:
void DoAction() throw(Error) { throw Error(); }
};
class Bar
{
public:
void DoAction() throw(Error) { throw Error(); }
};
void f (void)
{
Foo *foo = 0;
Bar *bar = 0;
try {
Automatic a;
foo = new Foo ();
foo->DoAction ();
bar = new Bar ();
bar->DoAction ();
delete foo;
delete bar;
// a's destructor invoked automatically after this
} catch (...) {
delete foo;
delete bar;
throw;
}
}
int main (int argc, char *argv[])
{
try {
f ();
} catch (Error &e) {
}
}
The code above works but the exact reason why it works is a bit complicated which is why I defered this discussion until now. The first thing to know is that the delete operator behaves nicely with regard to null pointers. That is, it is not an error to invoke delete on the null pointer. The second interesting point about this code is that the new Bar (); and new Foo () statements cannot half succeed. They either complete successfully in which case the return value of the expression is assigned to the left-hand side of the assignment or they fail completely in which case the left-hand side of the expression is not assigned.
The left-hand side of the expression is never assigned to if an expression is thrown because throwing the exception makes it impossible either for operator new to return or for the constructor to return. In both cases the code which assigns the return value of the expression is never given a chance to run.
Some readers might find the above discussion obvious but, I actually fell in this trap myself and it took me time to be convinced that this code could work so, I thought I should share my surprise :)
As already mentioned in the introduction, there are numerous sources of information on exceptions online. I have found a lot of them completely useless. Here is my list of what proved to be really enlightening:
http://www.octopull.demon.co.uk/c++/dragons/: Here be Dragons, by Alan Griffiths.
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndeepc/html/deep01202000.asp: Handling Exceptions, Part 14, by Robert Schmidt, January 20, 2000.
C++ exception handling for IA64, by Christophe de Dinechin. This paper describes the implementation now used in gcc.
http://www.freetype.org/david/reliable-c.html: Robust Design Techniques for C Programs, by David Turner.
http://www.kuzbass.ru:8086/docs/isocpp/: HTML version of the final draft of the C++ standard.