13. Exception Handling

13.1 Introduction

There was always a need to write robust and powerful programs that are resistant to run time errors, logic errors, and all other unexpected events. Exception handling is a relatively new and powerful tool that can be used for better and safer programming. As in many other areas, a good intention can lead to disastrous results. So, if you are not sure why and how to use exception handling, you are better off not using it at all.

13.2 C++ Exception Handling

C++ exception handling uses three statements (try, catch, and throw) added to the C++ language. With C++ exception handling, your program can propagate unexpected events to a higher execution context that is better able to recover from such abnormal events. These exceptions are handled by code outside the normal flow of control. The Microsoft C++ compiler implements the C++ exception-handling model based on the ISO WG21/ANSI X3J16 working papers toward the evolving standard for C++.

13.3 Structured Exception Handling

Structured exception handling is an extension to Microsoft C/C++ that can be used in either C or C++. Structured exception handling uses two constructs: try-except, known as exception handling, and try-finally, known as termination handling. The try-except statement enables applications to gain control of a program when events that normally terminate execution occur. The try-finally statement enables applications to guarantee execution of cleanup code when execution of a block of code is interrupted.

Note   Structured exception handling works with Microsoft Win32® for both C and C++ source files. However, it is not specifically designed for C++. Your code is more portable when using C++ exception handling and is also more flexible because it can handle exceptions of any type. For C++ programs, it is recommended that you use the new C++ exception-handling mechanism (try, catch, and throw statements).

13.4 Exception Handling Differences

The major difference between structured exception handling and C++ exception handling is that the C++ exception-handling model uses any data type, while the C structured exception-handling model uses type; unsigned int. That is, C exceptions are identified by an unsigned integer value, whereas C++ exceptions are identified by data type. When an exception is raised in C, each possible handler executes a filter, which examines the C exception context and determines whether to accept the exception, pass it to some other handler, or ignore it. When an exception is thrown in C++, it may be of any type.

A second difference is that the C structured exception handling model is referred to as asynchronous because exceptions occur secondary to the normal flow of control. The C++ exception handling mechanism is fully synchronous, which means that exceptions occur only when they are thrown.

If a C exception is raised in a C++ program, it can be handled by a structured exception handler with its associated filter or by a C++ catch handler, whichever is dynamically nearest to the exception context.

13.4.1 Using different exception handling mechanisms

The following example demonstrates how to use different exception handling mechanisms:

// Compile Options /GX
/**************************************************************
This simple program demonstrates how the asynchronous exceptions
could be caught by C++ exceptions handling mechanism
***************************************************************/
#include <stdio.h>

int *p = NULL;           // pointer used to generate an AV

class CMyObject {
    public:
        ~CMyObject () {
            printf ("Here is the destructor for CMyObject\n");
        }
};

void function1() {
    CMyObject ob;
    *p=3;                // causes an Access Violation exception
}

void function2() {
    try {
        function1();
    } catch (...) {
        printf("Caught an exception in function2()\n");
    }
}

int main (void) {
    function2 ();
    return 0;
}

Program Output:
Here is the destructor for CMyObject
Caught an exception in function1()
Caught an exception in function2()

13.4.2 The synchronous-exception handling model

Synchronous exceptions are objects thrown in C++ with a throw() statement. The synchronous exception-handling model is designed for catching synchronous exceptions only. The asynchronous exception-handling model is designed to catch synchronous exceptions and structured exception-handling type exceptions such as access violation and divides by zero.

The compiler can assume exception can only happen at function calls. This makes it a lot easier to optimize the code and allows the removal of exception handling tracking code for local unwindable objects whose scope doesn't span across a function call (or where all calls are inlined).

13.4.3 The asynchronous exception handling model

The synchronous exception-handling model is just an optimized version of the asynchronous exception-handling model. So the two models can be intermixed. The asynchronous exceptions can still be caught by the synchronous model with minor gotchas. The state tracking may not be quite up-to-date in the function where the exception occurs. This means that some of the local unwindable objects in the function that cause access violation may not get destructed, or an access violation in a function that has a try/catch may not get caught by this function.

13.4.4 _declspec(nothrow)

You can also tune your code by telling the compiler that a particular function never throws an exception by using _declspec(nothrow) on the function declaration, or by using the new C++ nothrow specification.

13.5 Major Points from the C++ Working Paper

    //Example:
    int f(int);
    class C {
            int i;
            double d;
        public:
            C(int, double);
    };

    C::C(int ii, double id) {
        try
            i(f(ii)), d(id)
        {
            // Constructor function body
        }
        catch (...)
        {
            // Handles exceptions thrown from the ctor-initializer
            // and from the constructor function body.
        }
    }

13.6 Throwing an Exception

Throwing an exception transfers control to a handler. An object is passed and the type of that object determines which handlers can catch it.

    //Example:
    throw "Help!";    // can be caught by a handler of some char* type: 
    try {
        // ...
    }
    catch(const char* p) {
        // Handle character string exceptions here.
    }
    // and
    class Overflow {
            // ...
    public:
        Overflow(char,double,double);
    };
    void f(double x) {
        // ...
        throw Overflow('+',x,3.45e107);
    }

This can be caught by a handler for exceptions of type Overflow, as follows:

    try { 
        // ... 
        f(1.2); 
        // ... 
    }
    catch (Overflow& oo) {
        // handle exceptions of type Overflow here
    }

When an exception is thrown, control is transferred to the nearest handler with an appropriate type. The nearest handler is the handler whose try block the thread of control most recently entered but has not yet exited.

A throw expression initializes a temporary object of the static type of the operand of throw and uses that temporary object to initialize the appropriately typed variable named in the handler.

The memory for the temporary copy of the exception being thrown is allocated in an implementation-defined way. The temporary persists as long as there is a handler being executed for that exception.

A throw expression with no operand rethrows the exception being handled without copying it. For example, code that must be executed because of an exception, yet cannot completely handle the exception, can be written as follows:

    // Example:
    try {
        // ...
    }
    catch (...) {
        // catch all exceptions
        // respond (partially) to exception
        throw;     // pass the exception to some
                   // other handler
    }

If no exception is presently being handled, executing a throw expression with no operand calls terminate().

13.7 Constructors and Destructors

As control passes from a throw point to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

An object that is partially constructed will have destructors executed only for its fully constructed subobjects. If a constructor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed.

The process of calling destructors for automatic objects constructed on the path from a try block to a throw expression is called stack unwinding.

13.8 Handling an Exception

The exception declaration in a handler describes the type(s) of exceptions that can cause that handler to be entered. The exception declaration shall not denote an incomplete type. Types shall not be defined in an exception declaration.

The handlers for a try block are tried in order of appearance. That makes it possible to write handlers that can never be executed—for example, by placing a handler for a derived class after a handler for a corresponding base class.

A ... in a handler exception declaration functions similarly to ... in a function parameter declaration; it specifies a match for any exception. If present, a ... handler shall be the last handler for its try block.

If no match is found among the handlers for a try block, the search for a matching handler continues in a dynamically surrounding try block.

An exception is considered handled upon entry to a handler. (Note: the stack will have been unwound at that point.)

If no matching handler is found in a program, the function terminate() is called. Whether or not the stack is unwound before calling, terminate() is implementation defined.

Referring to any nonstatic member or base class of an object in the function try block handler of a constructor or destructor for that object results in undefined behavior.

The fully constructed base classes and members of an object shall be destroyed before entering the function try block handler of a constructor or destructor for that object.

The scope and lifetime of the parameters of a function or constructor extend into the handlers of a function try block.

If the handlers of a function try block contain a jump into the body of a constructor or destructor, the program is ill-formed.

If a return statement appears in a handler of function try block of a constructor, the program is ill-formed.

The exception being handled is rethrown if control reaches the end of a handler of the function try block of a constructor or destructor. Otherwise, a function returns when control reaches the end of a handler for the function try block.

When the catch handler specifies a class object, a copy constructor is used to initialize a temporary object that is bound to the optionally specified name in the exception declaration for the catch handler. The object shall not have an abstract class type, because objects of those types shall not be created. That object is destroyed when the handler is exited, after the destruction of any automatic objects initialized within the handler. The copy constructor and destructor shall be accessible in the context of the catch handler. If the copy constructor and destructor are implicitly declared, such a use in the catch handler causes these functions to be implicitly defined; otherwise, the program shall provide a definition for these functions. If the use of a temporary object can be eliminated without changing the meaning of the program (except for execution of constructors and destructors associated with the use of the temporary object), the optional name can be bound directly to the temporary (or original) object specified in a throw expression that causes the catch handler to be executed. The copy constructor and destructor associated with the object shall be accessible even when the temporary object is eliminated.

When the catch handler specifies a nonconstant object, any changes to the object that are effected before the handler has exited are changes to the temporary copy for the handler. These changes will not affect the temporary (or original) object that was initialized by execution of the throw expression. When the catch handler specifies a reference to a nonconstant object, any changes to the referenced object are changes to the temporary (or original) object initialized when the throw expression was executed. These changes will have effect if that object is rethrown.

13.9 Exception Specifications

By using an exception specification as a suffix of its declarator, a function declaration lists exceptions that its function may directly or indirectly throw.

    exception-specification:
        throw ( type-id-listopt )
    type-id-list:
        type-id
        type-id-list, type-id

An exception specification shall appear only on a function declarator in a function, pointer declaration, or pointer definition. An exception specification shall not appear in a typedef declaration.

    //Example:
    void f() throw(int);                // OK
    void (*fp)() throw (int);           // OK
    void g(void pfa() throw(int));      // OK
    typedef int (*pf)() throw(int);     // Ill-formed

Note    Exception specification is optional and the absence of one does not impose restrictions on the possible function exceptions.

If any declaration of a function has an exception specification, all declarations, including the definition, of that function shall have an exception specification with the same set of type IDs. If a virtual function has an exception specification, all declarations, including the definition, of any function that override that virtual function in any derived class shall only allow exceptions that are allowed by the exception specification of the base-class virtual function.

    //Example:
    struct B {
        virtual void f() throw (int, double);
        virtual void g();
    };
    struct D: B {
        void f();                    // ill-formed
        void g() throw (int);        // OK
    };

The declaration of D::f is ill-formed because it allows all exceptions, whereas B::f allows only int and double. Similarly, any function or pointer-to-function assigned to, or initializing, a pointer-to-function shall only allow exceptions that are allowed by the pointer or function being assigned or initialized.

    //Example:
    void (*pf1)();        // no exception specification
    void (*pf2)() throw(A);
    void f() {
        pf1 = pf2;        // ok: pf1 is less restrictive
        pf2 = pf1;        // error: pf2 is more restrictive
    }

In such an assignment or initialization, exception specifications on return types and parameter types shall match exactly.

Calling a function through a declaration whose exception specification allows exceptions other than those allowed by the exception specification of the function definition is ill-formed. No diagnostic is required.

Types shall not be defined in exception specifications.

An exception specification can include the same class more than once and can include classes related by inheritance even though doing so is redundant. An exception specification can include identifiers that represent incomplete types. An exception specification can also include the name of the predefined class bad_exception.

If a class X is in the type-id-list of the exception specification of a function, that function is said to allow exception objects of class X or any class publicly and unambiguously derived from X. Similarly, if a pointer type Y* is in the type-id-list of the exception specification of a function, the function allows exceptions of type Y* or exceptions that are pointers to any type publicly and unambiguously derived from Y. Otherwise, a function only allows exceptions that have the same type as the types specified in the type-id-list of its exception specification.

Whenever an exception is thrown and the search for a handler encounters the outermost block of a function with an exception specification, the unexpected() function is called if the exception specification does not allow the exception.

    class X { };
    class Y { };
    class Z: public X { };
    class W { };
    void f() throw (X, Y) {
        int n = 0;
        if (n) throw X();        // OK
        if (n) throw Z();        // also OK
        throw W();               // will call unexpected()
    }

The unexpected() function may throw an exception that will satisfy the exception specification for which it was invoked (in this case, the search for another handler will continue at the call of the function with this exception specification) or it may call terminate.

An implementation shall not reject an expression simply because when it is executed it throws or might throw an exception that the containing function does not allow.

    extern void f() throw(X, Y);
    void g() throw(X) {
        f();        // OK
    }

The call to f is well formed even though when called, f may throw exception Y that g does not allow.

A function with no exception specification allows all exceptions. A function with an empty exception specification, throw(), does not allow any exceptions.

An exception specification is not considered part of a function type.

Note    There is no compile-time checking of function exception specification. It would be too complicated and too costly. For example, in order to check the exception propagation, the compiler would need to check the code of the called function, any function it calls, and so on.

13.10 Special Functions

The exception handling mechanism relies on two functions, terminate() and unexpected(), for coping with errors related to the exception handling mechanism itself.

13.10.1 The terminate() function

Exception handling must be abandoned for less subtle error handling techniques:

In such cases, void terminate(); is called.

13.10.2 The unexpected() function

If a function with an exception specification throws an exception that is not listed in the exception specification, the unexpected() function is called.

The unexpected() function shall not return, but it can throw (or rethrow) an exception. If it throws a new exception that is allowed by the previously violated exception specification, the search for another handler will continue at the call of the function whose exception specification was violated. If it throws or rethrows an exception that the exception specification does not allow, one of two things can happen. If the exception specification does not include the name of the predefined exception bad_exception, the terminate() function is called; otherwise, the thrown exception is replaced by an implementation-defined object of the type bad_exception and the search for another handler will continue at the call of the function whose exception specification was violated.

An exception specification guarantees that only the listed exceptions will be thrown. If the exception specification includes the name bad_exception, any exception not on the list may be replaced by bad_exception within the function unexpected().

13.10.3 The uncaught_exception() function

The following predicate returns true after completing evaluation of the object to be thrown until the exception declaration in the matching handle completes initialization. (This includes stack unwinding.)

    bool uncaught_exception();

13.11 Exceptions and Access

If the exception declaration in a catch clause has class type and the function in which the catch clause occurs does not have access to the destructor of that class, the program is ill-formed.

An object can be thrown if it can be copied and destroyed in the context of the function in which the throw expression occurs.