Getting Started | Documentation | Glish | Learn More | Programming | Contact Us |
Version 1.9 Build 1556 |
|
This section describes the exception mechanism from the perspective of a ``user'' of the system. It discusses the C++ interface, the mechanisms for automatic deletion of dynamically created objects, and the steps necessary to create new exception types whose objects can be thrown.
The C++ exceptions are based on the idea of try and catch blocks. The try block contains the code which may throw an exception, and the catch block, which must follow immediately after the try block, contains the handlers for the various exceptions which may be thrown in the try block. So in the simple case, one might have:
try { if (how_many == 1) throw(MajorError(1)); if (how_many == 2) throw(MinorError(2)); if (how_many == 3) throw(TypedError(3)); } catch (TypedError xx) { cout << "Caught TypedError" << endl; } catch (MinorError xx) { cout << "Caught MinorError" << endl; } catch (MajorError xx) { cout << "Caught MajorError" << endl; }In this example, depending on the variable how_many a different exception will be thrown, via throw(). The exception will then be caught by the first catch block that matches the exception. Thus, if MinorError was derived from TypedError, then the TypedError block would be executed before the MinorError block for a thrown MinorError.
It is also possible to have incremental recovery. A caught exception can be rethrown with throw. The following example illustrates this usage:
try { if (how_many == 1) throw(MajorError(1)); if (how_many == 2) throw(MinorError(2)); if (how_many == 3) throw(TypedError(3)); } catch (MinorError xx) { cout << "Caught MinorError" << endl; } catch (MajorError xx) { cout << "Caught MajorError" << endl; throw; } catch (TypedError xx) { cout << "Caught TypedError" << endl; }In this example, the MajorError catch block handles a MajorError when it occurs and then rethrows the exception so that it could be handled by other catch blocks. Thus, if MajorError is derived from TypedError, then the TypedError catch block would have the opportunity to handle its portion of the error after the MajorError catch block was finished.
These try/catch blocks can be nested to the level necessary as long as the introductory try is balanced with a closing brace. The following example contains a nested try block:
try { if (how_many == 0) throw(ExcpError(1)); try { if (how_many == 1) throw(ExcpError(1)); if (how_many == 2) throw(ExcpError(2)); if (how_many == 3) throw(ExcpError(3)); } catch (ExcpError xx) { throw(ExcpError(9)); } } catch (ExcpError xx) { cout << "Caught ExcpError" << endl; }
Another important portion of the exception handling mechanism is that it deletes the objects created between the point where the try/catch blocks are entered and the point where the exception is thrown. These object are automatically cleaned up when an exception occurs. The destructors are called for automatically created objects, but not for dynamically created objects which are not deleted by other objects.
The programmer should use the templated PtrHolder class to hold pointers to dynamically created objects. Because the PrtHolder object is automatically created, its destructor is called in case of an exception. That destructor deletes the pointer it holds, thus deletes the dynamically created object.
try { // True below means it's an array, False (the default) would mean // a singleton object. PtrHolder<Int> iholder(new Int[10000], True); some_function_that_throws_exception(); // pointer is deleted } catch (...) { cout << "Int array is nicely deleted" << endl; }Note that ... is standard C++ syntax and means any exception.
One of the advantages of the C++ exception mechanism is that one can design a class hierarchy of exceptions. This results in an a great amount of flexibility. The reason for this is that one can catch whole groups of exceptions with one catch block, and then handle them as appropriate. One can also catch an exception with a specific handler, and the rethrow the exception so that it can be caught with a more general handler.
The root of the exception hierarchy is the class AipsError. This class must be the base class for all new exception hierarchies. All of the exceptions which are part of the aips++ kernel, IndexError, AllocError, etc., are derived from the AipsError. Thus, if one wished, all exceptions resulting from the aips++ kernel could be caught by one catch block which catches AipsErrors. Generally, it is a good practice to derive all exceptions for a given library from a library specific exception class, e.g. TableError, which is in turn derived from AipsError.
AipsError offers the const member function getMesg() which returns the message stored with the exception. It can be used as:
try { ... } catch (AipsError& x) { cerr << "Unexpected exception: " << x.getMesg() << endl; }