Previous Next Contents Index Doc Set Home


Exception Handling

4


This chapter explains exception handling as currently implemented in the C++ compiler. It also identifies some areas of exception handling that are not clearly defined in the ANSI X3J16 draft working paper. Some of the topics covered in this chapter are interpretations of the draft. Other topics are specific to this compiler implementation.

For additional information on exception handling, see The C++ Programming Language (Second Edition) by Margaret A. Ellis and Bjarne Stroustrup.


Why Exception Handling?

Exceptions are anomalies that occur during the normal flow of a program and prevent it from continuing. These anomalies--user, logic, or system errors--can be detected by a function. If the detecting function cannot deal with the anomaly, it throws, an exception. A function that handles that kind of exception catches it.

In C++, when an exception is thrown, it cannot be ignored--there must be some kind of notification or termination of the program. If no user-provided exception handler is present, the compiler provides a default mechanism to terminate the program.


Using Exception Handling

There are three keywords for exception handling in C++:

try

A try block is a group of C++ statements, normally enclosed in braces { }, which may cause an exception. This grouping restricts exception handlers to exceptions generated within the try block.

catch

A catch block is a group of C++ statements that are used to handle a specific raised exception. Catch blocks, or handlers, should be placed after each try block. A catch block is specified by:

throw

The throw statement is used to throw an exception to a subsequent exception handler. A throw statement is specified with:

An Example

Code  Example  4-1     Exception Handling Example
class Overflow {
   // ...
public:
   Overflow(char,double,double);
};

void f(double x)
{
   // ...
   throw Overflow('+',x,3.45e107);
}

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

In this example, the function call in the try block passes control to f(), which throws an exception of type Overflow. This exception is handled by the catch block, which handles type Overflow exceptions.


Implementing Exception Handlers

Here are the basic tasks involved in implementing exception handlers:

Synchronous Exception Handling

Exception handling is designed to support only synchronous exceptions, such as array range checks. The term synchronous exception means that exceptions can only be originated from throw expressions.

The current draft supports synchronous exception handling with termination model. Termination means that once an exception is thrown, control never returns to the throw point.

Asynchronous Exception Handling

Exception handling is not designed to directly handle asynchronous exceptions such as keyboard interrupts. However, exception handling can be made to work in the presence of asynchronous events if care is taken. For instance, to make exception handling work with signals, you can write a signal handler that sets a global variable, have another routine that polls the value of that variable at regular intervals, and throws an exception when the value changes.


Flow of Control

In C++, exception handlers do not correct the exception and then return to the point at which the exception occurred. Instead, when an exception is generated, control is passed out of the function that threw the exception, out of the try block that anticipated the exception, and into the catch block whose exception declaration matches the exception thrown.

The catch block handles the exception. It either rethrows the exception, branches to a label, or ends normally. If a catch block ends normally, without a throw, the flow of control passes over all subsequent catch blocks.

Whenever an exception is thrown and caught, and control is returned outside of the function that threw the exception, stack unwinding takes place. During stack unwinding, any automatic objects that were created within the scope of that function are safely destroyed via calls to their destructors.

If a try block ends without an exception, all subsequent catch blocks are ignored.


Note - An exception handler cannot return control to the source of the error by using the return statement. A return issued in this context returns from the function containing the catch block.

Branching Into and Out of try Blocks and Handlers

Branching out of a try block or a handler is allowed. Branching into a catch block is not allowed, however, because that is equivalent to jumping past an initiation of the exception.

Nesting of Exceptions

Nesting of exceptions occurs when exceptions are thrown in a handler. The handled exception needs to be kept around so that it can be rethrown. Nesting also occurs when exceptions are thrown in the destructor during stack unwinding.

Using throw in a Function Declaration

A function declaration can include an exception specification, a list of exceptions that a function may directly or indirectly throw.

This declaration indicates to the caller that the function foo generates only one exception, and that it is caught by a handler of type X:

void foo(int) throw(X);

A variation on the previous example is:

void foo(int) throw();

This declaration guarantees that no exception is generated by the function foo. If an exception occurs, it results in a call to the predefined function unexpected(). By default, unexpected() calls abort() to exit the program. This default behavior can be changed by calling the set_unexpected() function; see "set_terminate() and set_unexpected() Functions" on page 103.

The check for unexpected exceptions is done at program execution time, not at compile time. The compiler may, however, eliminate unnecessary checking in some simple cases.

For instance, no checking for f is generated in the following example:

void foo(int) throw(x);
void f(int) throw(x);
{
	foo(13);
}

The absence of an exception specification allows any exception to be thrown.


Runtime Errors

There are five runtime error messages associated with exceptions:

1. No handler for the exception.

2. Unexpected exception thrown.

3. An exception can only be re-thrown in a handler.

4. During stack unwinding, a destructor must handle its own exception.

5. Out of memory.

When errors are detected at runtime, the error message displays the type of the current exception and one of the above messages. The predefined function terminate() is then called, which calls abort() by default.


Caution - Selecting a terminate() function that does not terminate is an error.
The class xunexpected is now defined in exception.h.

The compiler makes use of the information provided in the exception specification in optimizing code production. For instance, table entries for functions that do not throw exceptions are suppressed, and runtime checkings for exception specifications of functions are eliminated wherever possible. Thus, declaring functions with correct exception specifications can lead to better code generation.


set_terminate() and set_unexpected() Functions

The following sections describe how to modify the behavior of the terminate() and unexpected() functions using set_terminate() and set_unexpected().

set_terminate()

You can modify the default behavior of terminate() by calling the function set_terminate():

typedef void (*PFV)();
PFV set_terminate(PFV);

terminate() calls the function passed as an argument to set_terminate(). The function passed in the most recent call to set_terminate() is called. The previous function passed as an argument to set_terminate() is the return value, so you can implement a stack strategy for using terminate().

set_unexpected()

You can modify the default behavior of unexpected() by calling the function set_unexpected():

typedef void (*PFV)()
PFV set_unexpected(PFV);

unexpected() calls the function passed as an argument to set_unexpected(). The function passed in the most recent call to set_unexpected() is called. The previous function passed as an argument to set_unexpected() is the return value; so you can implement a stack strategy for using unexpected().


Matching Exceptions With Handlers

A handler type T matches a throw type E if any of the following is true:

1. T is same as E

2. T is const or volatile of E

3. E is const or volatile of T

4. T is ref of E or E is ref of T

5. T is a public base of E

6. T and E are both pointer types, and E can be converted to T by standard pointer conversion.

Throwing exceptions of reference or pointer types can result in a dangling pointer.

While handlers of type (X) and (X&) both match an exception of type X, the semantics are different. Using a handler with type (X) invokes the object's copy constructor and possibly truncates the object, which can happen when the exception is derived from X.

Handlers for a try block are tried in the order of their appearance. Handlers for a derived class (or a pointer to a reference to a derived class) must precede handlers for the base class to ensure that the handler for the derived class is invoked.


Access Control in Exceptions

The compiler performs the following check on access control on exceptions:

1. The formal argument of a catch clause obeys the same rules as an argument of the function in which the catch clause occurs.

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

Currently, access controls do not affect matching.

No other access is checked at runtime except for the matching rule described in "Matching Exceptions With Handlers" on page 104.


-noex Compiler Option

If you know that exceptions are not used, use the compiler option -noex to suppress generation of code that supports exception handling. The use of -noex results in smaller code size and faster code execution. When files compiled with -noex are linked to files compiled without -noex, some local objects are not destroyed when exceptions occur. By default, the compiler generates code to support exception handling.


New Runtime Function and Predefined Exceptions

You can use several functions related to exception handling. The header file exception.h includes the predefined exceptions xmsg and xalloc. The definitions provided in exception.h differ from those in the X3J16 Draft Working Paper.


Default new-handler() Function

When ::operator new() cannot allocate storage, it calls the currently installed new-handler function. The default new-handler function throws an xalloc exception.


Note - The old behavior was to return a null from ::operator new() when a memory request could not be satisfied. To restore the old behavior, call set_new_handler(0).


Building Shared Libraries With Exceptions

When shared libraries are opened with dlopen, RTLD_GLOBAL must be used for exceptions to work.


Using Exceptions in a Multithreaded Environment

The current exception-handling implementation is safe for multithreading--exceptions in one thread do not interfere with exceptions in other threads. However, you cannot use exceptions to communicate across threads; an exception thrown from one thread cannot be caught in another.

Each thread can set its own terminate() or unexpected() function. Calling set_terminate() or set_unexpected() in one thread affects only the exceptions in that thread. The default function for terminate() is abort() for the main thread and thr_exit() for other threads. See "set_terminate() and set_unexpected() Functions" on page 103.




Previous Next Contents Index Doc Set Home