[Previous]  [Up]  [Next]

Implementation notes

Exception handling



A reasonably usable exception handling mechanism is provided. Fully implementing the semantics of exceptions is impossible without compiler support, but with a small amount of user intervention some important uses can be accomodated.

The mechanism is implemented using setjmp() and longjmp() hidden behind macros. Consequently it's quite heavy-weight, both in code space and execution time even when exceptions are not thrown. As such, only the streams components currently use exception handling extensively, on the grounds that this is a case where genuine runtime errors (as opposed to programmer errors) are likely; where cleanup is likely to be important; and where the execution cost of the exception handling mechanism is likely to be masked by the cost of expensive external I/O operations.

Destruction of stack based objects during stack unwinding is supported, but requires that objects to be destroyed be explicitly tagged. Catching of exceptions based on the dynamic type of the thrown exception is implemented using the runtime type identification mechanism (see previous section). This implies that only objects of class type can be thrown... and, in fact, all exception classes must have the standard class exception as a publicly accessible base.

Try blocks can be nested in handlers, either directly, or indirectly via function calls from handler bodies to functions which may throw exceptions. For example:

    #include <stdlib.h>         // for rand()
    #include "exception.h"      // for exception handling macros
    #include "iostream.h"       // for cout
    #include "stdexcept.h"      // for logic_error, runtime_error
    
    #include "autodstry.c++"    // for destruction during stack
                                // unwinding need not be included if
                                // that behaviour not required
    
    class Foo
    {
      // etc ...
    }
    
    inline void destroy(Foo* p)  // CFront template workaround
    {
      p->~Foo();
    }
    
    void some_fn()
    {
      Foo a_foo;
      DESTROY_ON_THROW(a_foo);   // a_foo will be destroyed during
                                 // stack unwinding
    
      if(rand()%2)
        throw(logic_error("from some_fn"));
    }
    
    void other_fn()
    {
      try
      {
        Foo another_foo;
        DESTROY_ON_THROW(another_foo); // another_foo will be
                                       // destroyed during stack
                                       // unwinding
        some_fn();
        throw(runtime_error("from other_fn"));
      }
      BEGIN_HANDLERS
      catch(logic_error, ex)
      {
        cout << "caught a logic_error: " << ex.what() << endl;
    
        rethrow;                // rethrow to next enclosing
                                // try/catch block or call
                                // terminate() if none
      }
      catch(runtime_error, ex)
      {
        cout << "caught a runtime_error: " << ex.what() << endl;
    
        some_fn();              // calls some_fn() again. If it
                                // throws, the exception will
                                // propagate to the next enclosing
                                // try/catch block or call
                                // terminate() if none
    
      }
      catch_DOTS                // catch any exception
      {
        cout << "caught some other exception" << endl;
      }
      END_HANDLERS              // if the current exception were
                                // uncaught it would propagate to
                                // the next enclosing try/catch
                                // block or call terminate() if none
    }

Examples of exception handling can be found in test.texcept and test.trtti.


 [Previous]  [Up]  [Next]