This chapter explains exception handling as currently implemented in the Sun C++ compiler, and the requirements of the C++ International Standard.
For additional information on exception handling, see The C++ Programming Language, third edition, by Bjarne Stroustrup, Addison Wesley, 1997.
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.
Exception handling is expensive compared to ordinary program flow controls, such as loops or if-statements. It is therefore better not to use the exception mechanism to deal with ordinary situations, but to reserve it for situations that are truly unusual.
Exceptions are particularly helpful in dealing with situations that cannot be handled locally. Instead of propagating error status throughout the program, you can transfer control directly to the point where the error can be handled.
For example, a function might have the job of opening a file and initializing some associated data. If the file cannot be opened or is corrupted, the function cannot do its job. However, that function might not have enough information to handle the problem. The function can throw an exception object that describes the problem, transferring control to an earlier point in the program. The exception handler might automatically try a backup file, query the user for another file to try, or shut down the program gracefully. Without exception handlers, status and data would have to be passed down and up the function call hierarchy, with status checks after every function call. With exception handlers, the flow of control is not obscured by error checking. If a function returns, the caller can be certain that it succeeded.
Exception handlers have disadvantages. If a function does not return because it, or some other function it called, threw an exception, data might be left in an inconsistent state. You need to know when an exception might be thrown, and whether the exception might have a bad effect on the program state.
There are three keywords for exception handling in C++:
try
catch
throw
A try block is a group of C++ statements, normally enclosed in braces { }, which might cause an exception. This grouping restricts exception handlers to exceptions generated within the try block. Each try block has one or more associated catch blocks.
A catch block is a group of C++ statements that are used to handle a specific thrown exception. One or more catch blocks, or handlers, should be placed after each try block. A catch block is specified by:
The keyword catch
A catch parameter, enclosed in parentheses (), which corresponds to a specific type of exception that may be thrown by the try block
A group of statements, enclosed in braces { }, whose purpose is to handle the exception
The throw statement is used to throw an exception and its value to a subsequent exception handler. A regular throw consists of the keyword throw and an expression. The result type of the expression determines which catch block receives control. Within a catch block, the current exception and value may be re-thrown simply by specifying the throw keyword alone (with no expression).
In the following 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.
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 } }
To implement an exception handler, do these basic tasks:
When a function is called by many other functions, code it so that an exception is thrown whenever an error is detected. The throw expression throws an object. This object is used to identify the types of exceptions and to pass specific information about the exception that has been thrown.
Use the try statement in a client program to anticipate exceptions. Enclose function calls that might produce an exception in a try block.
Code one or more catch blocks immediately after the try block. Each catch block identifies what type or class of objects it is capable of catching. When an object is thrown by the exception, this is what takes place:
If the object thrown by the exception matches the type of the catch expression, control passes to that catch block.
If the object thrown by the exception does not match the first catch block, subsequent catch blocks are searched for a matching type.
If try blocks are nested, and there is no match, control passes from the innermost catch block to the nearest catch block surrounding the try block.
If no matching catch block is found in the current function, any automatic (local nonstatic) objects in the current function are destroyed and the function exits immediately. A search for a matching catch block continues with the function that called the current function. This process continues up to function main.
If there is no match in any of the catch blocks, the program is normally terminated with a call to the predefined function terminate(). By default, terminate() calls abort(), which destroys all remaining objects and exits from the program. This default behavior can be changed by calling the set_terminate() function.
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 C++ standard supports synchronous exception handling with a termination model. Termination means that once an exception is thrown, control never returns to the throw point.
Exception handling is not designed to directly handle asynchronous exceptions such as keyboard interrupts. However, you can make exception handling work in the presence of asynchronous events if you are careful. For instance, to make exception handling work with signals, you can write a signal handler that sets a global variable, and create another routine that polls the value of that variable at regular intervals and throws an exception when the value changes.
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 block 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 might rethrow the same exception, throw another exception, jump to a label, return from the function, or end normally. If a catch block ends normally, without a throw, the flow of control passes over all other catch blocks associated with the try block.
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 the block that was exited are safely destroyed via calls to their destructors.
If a try block ends without an exception, all associated catch blocks are ignored.
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 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, that is, throwing an exception while another remains unhandled, is allowed only in restricted circumstances. From the point when an exception is thrown to the point when the matching catch clause is entered, the exception is unhandled. Functions that are called along the way, such as destructors of automatic objects being destroyed, may throw new exceptions, as long as the exception does not escape the function. If a function exits via an exception while another exception remains unhandled, the terminate() function is called immediately.
Once an exception handler has been entered, the exception is considered handled, and exceptions may be thrown again.
You can determine whether any exception has been thrown and is currently unhandled. See "Calling the uncaught_exception() Function".
A function declaration can include an exception specification, a list of exceptions that a function may throw, directly or indirectly.
The two following declarations indicate to the caller that the function f1 generates only exceptions that can be caught by a handler of type X, and that the function f2 generates only exceptions that can be caught by handlers of type W, Y, or Z:
void f1(int) throw(X); void f2(int) throw(W,Y,Z);
A variation on the previous example is:
void f3(int) throw(); // empty parentheses
This declaration guarantees that no exception is generated by the function f3. If a function exits via any exception that is not allowed by an exception specification, it results in a call to the predefined function unexpected(). By default, unexpected() calls abort() to exit the program. You can change this default behavior by calling the set_unexpected() function. See "set_unexpected() ".
The check for unexpected exceptions is done at program execution time, not at compile time. Even if it appears that a disallowed exception might be thrown, there is no error unless the disallowed exception is actually thrown at runtime.
The compiler can, 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.
There are five runtime error messages associated with exceptions:
No handler for the exception
Unexpected exception thrown
An exception can only be re-thrown in a handler
During stack unwinding, a destructor must handle its own exception
Out of memory
When errors are detected at runtime, the error message displays the type of the current exception and one of the five error messages. By default, the predefined function terminate() is called, which then calls abort().
The compiler uses the information provided in the exception specification to optimize code production. For example, table entries for functions that do not throw exceptions are suppressed, and runtime checking for exception specifications of functions is eliminated wherever possible. Thus, declaring functions with correct exception specifications can lead to better code generation.
The following sections describe how to modify the behavior of the terminate() and unexpected() functions using set_terminate() and set_unexpected().
You can modify the default behavior of terminate() by calling the function set_terminate(), as shown in the following example.
// declarations are in standard header <exception> namespace std { typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler f) throw(); void terminate(); }
The terminate() function is called in any of the following circumstances:
The exception handling mechanism calls a user function (including destructors for automatic objects) that exits via an uncaught exception while another exception remains uncaught.
The exception handling mechanism cannot find a handler for a thrown exception.
The construction or destruction of a nonlocal object with static storage duration exits using an exception.
Execution of a function registered with atexit() exits using an exception.
A throw expression with no operand attempts to rethrow an exception and no exception is being handled.
The unexpected() function throws an exception that is not allowed by the previously violated exception specification, and std::bad_exception is not included in that exception specification.
The default version of unexpected() is called.
The terminate() function calls the function passed as an argument to set_terminate(). Such a function takes no parameters, returns no value, and must terminate the program (or the current thread). 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(). The default function for terminate() calls abort() for the main thread and thr_exit() for other threads. Note that thr_exit() does not unwind the stack or call C++ destructors for automatic objects.
Selecting a terminate() replacement that returns to its caller, or that does not terminate the program or thread, is an error.
You can modify the default behavior of unexpected() by calling the function set_unexpected():
// declarations are in standard header <exception> namespace std { class exception; class bad_exception; typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler f) throw(); void unexpected(); }
The unexpected() function is called when a function attempts to exit via an exception not listed in its exception specification. The default version of unexpected() calls terminate().
A replacement version of unexpected() might throw an exception permitted by the violated exception specification. If it does so, exception handling continues as though the original function had really thrown the replacement exception. If the replacement for unexpected() throws any other exception, that exception is replaced by the standard exception std::bad_exception. If the original function's exception specification does not allow std::bad_exception, function terminate() is called immediately. Otherwise, exception handling continues as though the original function had really thrown std::bad_exception.
unexpected() calls the function passed as an argument to set_unexpected(). Such a function takes no parameters, returns no value, and must not return to its caller. 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().
Selecting an unexpected() replacement that returns to its caller is an error.
An uncaught, or active, exception is an exception that has been thrown, but not yet accepted by a handler. The function uncaught_exception() returns true if there is an uncaught exception, and false otherwise.
The uncaught_exception() function is most useful for preventing program termination due to a function that exits with an uncaught exception while another exception is still active. This situation most commonly occurs when a destructor called during stack unwinding throws an exception. To prevent this situation, make sure uncaught_exception() returns false before throwing an exception within a destructor. (Another way to prevent program termination due to a destructor throwing an exception while another exception is still active is to design your program so that destructors do not need to throw exceptions.)
A handler type T matches a throw type E if any of the following is true:
T is the same as E.
T is const or volatile of E.
E is const or volatile of T.
T is ref of E or E is ref of T.
T is a public base class of E.
T and E are both pointer types, and E can be converted to T by a standard pointer conversion.
Throwing exceptions of reference or pointer types can result in a dangling pointer if the object pointed or referred to is destroyed before exception handling is complete. When an object is thrown, a copy of the object is always made through the copy constructor, and the copy is passed to the catch block. It is therefore safe to throw a local or temporary object.
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 (again). If the thrown object is of a type derived from the handler type, the object is truncated. Catching a class object by reference therefore usually executes faster.
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 can be invoked.
The compiler performs the following check on access control on exceptions:
The formal argument of a catch clause obeys the same rules as an argument of the function in which the catch clause occurs.
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".
If the constructor for a base class or member of a class T exits via an exception, there would ordinarily be no way for the T constructor to detect or handle the exception. The exception would be thrown before the body of the T constructor is entered, and thus before any try block in T could be entered.
A new feature in C++ is the ability to enclose an entire function in a try block. For ordinary functions, the effect is no different from placing the body of the function in a try block. But for a constructor, the try block traps any exceptions that escape from initializers of base classes and members of the constructor's class. When the entire function is enclosed in a try block, the block is called a function try block.
In the following example, any exception thrown from the constructor of base class B or member e is caught before the body of the T constructor is entered, and is handled by the matching catch block.
You cannot use a return statement in the handler of a function try block, because the catch block is outside the function. You can only throw an exception or terminate the program by calling exit() or terminate().
class B { ... }; class E { ... }; class T : public B { public: T(); private: E e; }; T::T() try : B(args), E(args) { ... // body of constructor} catch( X& x ) { ... // handle exception X} catch( ... ) { ... // handle any other exception}
If you know that exceptions are not used in a program, you can use the compiler option -features=no%except to suppress generation of code that supports exception handling. The use of the option results in slightly smaller code size and faster code execution. However, when files compiled with exceptions disabled are linked to files using exceptions, some local objects in the files compiled with exceptions disabled are not destroyed when exceptions occur. By default, the compiler generates code to support exception handling. Unless the time and space overhead is important, it is usually better to leave exceptions enabled.
The standard header <exception> provides the classes and exception-related functions specified in the C++ standard. You can access this header only when compiling in standard mode (compiler default mode, or with option -compat=5). The header provides the following declarations:
// standard header <exception> namespace std { class exception { exception() throw(); exception(const exception&) throw(); exception& operator=(const exception&) throw(); virtual ~exception() throw(); virtual const char* what() const throw(); }; class bad_exception: public exception { ... }; // Unexpected exception handling typedef void (*unexpected_handler)(); unexpected_handler set_unexpected(unexpected_handler) throw(); void unexpected(); // Termination handling typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler) throw(); void terminate(); bool uncaught_exception() throw(); }
The standard class exception is the base class for all exceptions thrown by selected language constructs or by the C++ standard library. An object of type exception can be constructed, copied, and destroyed without generating an exception. The virtual member function what() returns a character string that describes the exception.
For compatibility with exceptions as used in C++ release 4.2, the header <exception.h> is also provided for use in standard mode. This header allows for a transition to standard C++ code and contains declarations that are not part of standard C++. Update your code to follow the C++ standard (using <exception> instead of <exception.h>) as development schedules permit.
// header <exception.h>, used for transition #include <exception> #include <new> using std::exception; using std::bad_exception; using std::set_unexpected; using std::unexpected; using std::set_terminate; using std::terminate; typedef std::exception xmsg; typedef std::bad_exception xunexpected; typedef std::bad_alloc xalloc;
In compatibility mode (option -compat=4), header <exception> is not available, and header <exception.h> refers to the same header provided with C++ release 4.2. It is not reproduced here.
When shared libraries are opened with dlopen, you must use RTLD_GLOBAL for exceptions to work.
When building shared libraries with exceptions in them, do not pass the option -Bsymbolic to ld. Exceptions that should be caught might be missed.
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 "Specifying Runtime Errors").
Thread cancellation (pthread_cancel(3T)) results in the destruction of automatic (local nonstatic) objects on the stack. When a thread is cancelled, the execution of local destructors is interleaved with the execution of cleanup routines that the user has registered with pthread_cleanup_push(). The local objects for functions called after a particular cleanup routine is registered are destroyed before that routine is executed.