Reusable exception handlers in C++


Any sufficiently well marketed product is
indistinguishable from crap.

-- a reformulation of Clarke's Third law

Abstracting exception handlers

Consider the following code, which is what most textbooks on C++ give as an example for how to use exception handling:
void MyClass::EstablishAllConnections() 
{
  try {
    f(); // f() may throw ArithmeticException
    g(); // g() may throw NetworkConnectionException
    h(); // h() may throw DatabaseException
  }
  catch (ArithmeticException &ae)
  {
    HandleArithmeticException(ae);
  }
  catch (NetworkConnectionException &nce)
  {
    HandleNetworkingException(nce);
  }
  catch (DatabaseException &dbe)
  {
    HandleDatabaseException(dbe);
  }
}
Now there are several problems with this code. To see it, consider how you would write another function, that also used the g() to establish another network connection. Of course, you would write something like this:
void MyClass::PromptHostAndConnect()
{
  try
  {
    a(); // a() may throw UserInputException.
    g(); // g() may throw NetworkConnectionException
  }
  catch (UserInputException &uie)
  {
    HandleUserInputException(uie);
  }
  catch (NetworkConnectionException &nce)
  {
    HandleNetworkConnectionException(nce);
  }
}
See? Now there are two copies of exactly similar duplicated code - but only as part of an exception handler. Even though we've carefully factored the common code into a separate function HandleNetworkConnectionException, there are still significant amount of duplicated code in these functions. We can do better. First, we move most of the exception handling code into a single function:
void MyClass::HandleExceptions()
{
  try
  {
    throw;
  }
  catch (UserInputException &uie)
  {
    // ...
  }
  catch (NetworkConnectionException &nce)
  {
    // ...
  }
  catch (DatabaseException &dbe)
  {
    // ...
  }
  catch (ArithmeticException &ae)
  {
    // ...
  }
}

Now we've built a function, which will first rethrow the active exception, then catch it, and handle it properly. Now we can use it to invoke the reusable exception handlers as follows:
void MyClass::EstablishAllConnections()
{
  try {
    f(); // f() may throw ArithmeticException
    g(); // g() may throw NetworkConnectionException
    h(); // h() may throw DatabaseException
  }
  catch (...)
  {
    HandleExceptions();
  }
}
Note that here, we do not need to know the exact type of the exception - the 'HandleExceptions' function can take care of all that for us.

This technique, although it is very simple, is often overlooked. Maybe this is because it's too easy to write a separate exception handler for all functions. Or maybe it's because nobody thought of reusing those exception handlers. Or it might be because not all compilers have traditionally supported this. Nonetheless, abstracting away common exception handlers is very efficient means of clearing up exceptions-filled code.

Separating normal and exceptional flow of control

Now, let me focus on a related, but different problem in writing exception handlers. Often you have a sequence of functions, like above, each of which may throw exceptions. Often, more than one of them throw the same types of exceptions, but you still would like to handle each of them separately - you usually need to know which function threw the exception to be able to handle it correctly. For example, you might need information about the name of the function that threw the exception in order to add information about it to the message - this way you can build a kind of high-level call stack about those exceptions.

The easy way of getting at it is:

void MyClass::EstablishAllConnections()
{

  try {
    f1(); // f1() may throw ArithmeticException
  }
  catch (ArithmeticException &ae)
  {
    cleanup1();
    cleanupAll();
  }
  try {
    f2(); // f2() may also throw ArithmeticException
  }
  catch (ArithmeticException &ae)
  {
    cleanup2();
    cleanup1();
    cleanupAll();
  }
}
[Note: the name "cleanup" is not intended to convey that this would only reprsent cleanup actions, you can also think of those as placeholders for the exception handling code. Note however that there are portions in both catch blocks that are exactly the same. This is unavoidable in this kind of code.] See how messy this approach gets? Even though we again went to great lengths trying to ensure we have factored the common code into a separate functions, we ended up in an unreadable mess. When you have a sequence of five or more of these, the size of the function is already too unwieldy to maintain. So something better is needed. Here's a better alternative:
void MyClass::EstablishAllConnections()
{
  int operationsDone = 0;
  try {
    f1(); operationsDone = 10;
    f2(); operationsDone = 20;
  }
  catch (...)
  {
    if (operationsDone >= 20) { cleanup2(); }
    if (operationsDone >= 10) { cleanup1(); }
    cleanupAll();
  }
}
Here we use a separate integer variable for keeping track of which operations have been successfully completed. This will separate error handling code away from the actual algorithm, and therefore eases understanding and readability of this code. I received a comment from Yves Dufournaud that it is possible to improve the above a bit:
void MyClass::EstablishAllConnections()
{
  int operationsDone = 0;
  try {
    f1(); ++operationsDone;
    f2(); ++operationsDone;
    ...
  }
  catch (...)
  {
    switch( operationsDone)
    {
     ... // in reverse order
     case 2: cleanup2();
     case 1: cleanup1(); 
     default: cleanupAll();    
  }
}
This is a form of simplification to the previous approach, intended to make the code even clearer than the previous. It should be noted however that there are several subtle aspects related to the choice between these versions. First,

References

[1] Working paper for Draft Proposed International Standard for Information Systems -- Programming language C++, http://www.maths.warwick.ac.uk/c++/pub/wp/html/cd2

[2]Glen McCluskey & Associates LLC has a report of a ANSI/ISO standards meeting discussing this idea.



Back...
©Esa Pulkkinen (esa.pulkkinen@kotiposti.net)
This document may be reproduced or distributed without permission of the author.

$Id: reusable-exception-handler.html,v 1.4 1999/06/20 13:19:40 esap Exp $