______________________________________________________________________ 15 Exception handling [except] ______________________________________________________________________ 1 Exception handling provides a way of transferring control and informa- tion from a point in the execution of a program to an exception han- dler associated with a point previously passed by the execution. A handler will be invoked only by a throw-expression invoked in code executed in the handler's try block or in functions called from the handler's try block . try-block: try compound-statement handler-seq function-try-block: try ctor-initializeropt function-body handler-seq handler-seq: handler handler-seqopt handler: catch ( exception-declaration ) compound-statement exception-declaration: type-specifier-seq declarator type-specifier-seq abstract-declarator type-specifier-seq ... throw-expression: throw assignment-expressionopt A try-block is a statement (clause _stmt.stmt_). A throw-expression is of type void. Code that executes a throw-expression is said to "throw an exception;" code that subsequently gets control is called a "handler." [Note: within this clause "try block" is taken to mean both try-block and function-try-block. ] 2 A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one. When this happens, each variable declared in the try block will be destroyed in the context that directly contains its declaration. [Example: lab: try { T1 t1; try { T2 t2; if (condition) goto lab; } catch(...) { /* handler 2 */ } } catch(...) { /* handler 1 */ } Here, executing goto lab; will destroy first t2, then t1, assuming the condition does not declare a variable. Any exception raised while destroying t2 will result in executing handler 2; any exception raised while destroying t1 will result in executing handler 1. ] 3 A function-try-block associates a handler-seq with the ctor-initial- izer, if present, and the function-body. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the function-body transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other han- dlers. [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 } --end example] 15.1 Throwing an exception [except.throw] 1 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 const 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); } can be caught by a handler for exceptions of type Overflow try { // ... f(1.2); // ... } catch(Overflow& oo) { // handle exceptions of type Overflow here } --end example] 2 When an exception is thrown, control is transferred to the nearest handler with a matching type (_except.handle_); "nearest" means the handler for which the compound-statement ctor-initializer, or func- tion-body following the try keyword was most recently entered by the thread of control and not yet exited. 3 A throw-expression initializes a temporary object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from "array of T" or "function returning T" to "pointer to T" or "pointer to function returning T", respectively. [Note: the temporary object created for a throw-expression that is a string literal is never of type char* or wchar_t*; that is, the special conversions for string literals from the types "array of const char" and "array of const wchar_t" to the types "pointer to char" and "pointer to wchar_t", respectively (_conv.array_), are never applied to a I. throw-expression . ] The temporary is used to initialize the variable named in the matching handler (_except.handle_). The type of the throw-expression shall not be an incomplete type, or a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. Except for these restrictions and the restrictions on type matching mentioned in _except.handle_, the operand of throw is treated exactly as a function argument in a call (_expr.call_) or the operand of a return statement. 4 The memory for the temporary copy of the exception being thrown is allocated in an unspecified way, except as noted in _basic.stc.dynamic.allocation_. The temporary persists as long as there is a handler being executed for that exception. In particular, if a handler exits by executing a throw; statement, that passes con- trol to another handler for the same exception, so the temporary remains. When the last handler being executed for the exception exits by any means other than throw; the temporary object is destroyed and the implementation may deallocate the memory for the temporary object; any such deallocation is done in an unspecified way. The destruction occurs immediately after the destruction of the object declared in the exception-declaration in the handler. 5 If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (_class.temporary_), then the exception in the handler can be initial- ized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill- formed (even when the temporary object could otherwise be eliminated). Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated). 6 A throw-expression with no operand rethrows the exception being han- dled. The exception is reactivated with the existing temporary; no new temporary exception object is created. The exception is no longer considered to be caught; therefore, the value of uncaught_exception() will again be true. [Example: code that must be executed because of an exception yet cannot completely handle the exception can be written like this: try { // ... } catch (...) { // catch all exceptions // respond (partially) to exception throw; // pass the exception to some // other handler } --end example] 7 The exception thrown is the one most recently caught and not finished. An exception is considered caught when initialization is complete for the formal parameter of the corresponding catch clause, or when termi- nate() or unexpected() is entered due to a throw. An exception is considered finished when the corresponding catch clause exits or when unexpected() exits after being entered due to a throw. 8 If no exception is presently being handled, executing a throw-expres- sion with no operand calls terminate() (_except.terminate_). 15.2 Constructors and destructors [except.ctor] 1 As control passes from a throw-expression 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. 2 An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed subobjects, that is, for subobjects for which the constructor has completed execu- tion and the destructor has not yet begun execution. Should a con- structor for an element of an automatic array throw an exception, only the constructed elements of that array will be destroyed. If the object or array was allocated in a new-expression and the new-expres- sion does not contain a new-placement, the deallocation function (_basic.stc.dynamic.deallocation_, _class.free_) is called to free the storage occupied by the object; the deallocation function is chosen as specified in _expr.new_. If the object or array was allocated in a new-expression and the new-expression contains a new-placement, the storage occupied by the object is deallocated only if an appropriate placement operator delete is found, as specified in _expr.new_. 3 The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called stack unwinding." [Note: If a destructor called during stack unwinding exits with an exception, terminate is called (_except.terminate_). So destructors should generally catch exceptions and not let them propa- gate out of the destructor. --end note] 15.3 Handling an exception [except.handle] 1 The exception-declaration in a handler describes the type(s) of excep- tions that can cause that handler to be entered. The exception-decla- ration shall not denote an incomplete type. The exception-declaration shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. Types shall not be defined in an exception-declaration. 2 A handler of type "array of T" or "function returning T" is adjusted to be of type "pointer to T" or "pointer to function returning T", respectively. 3 A handler is a match for a throw-expression with an object of type E if --The handler is of type cv T or cv T& and E and T are the same type (ignoring the top-level cv-qualifiers), or --the handler is of type cv T or cv T& and T is an unambiguous public base class of E, or --the handler is of type cv1 T* cv2 and E is a pointer type that can be converted to the type of the handler by either or both of --a standard pointer conversion (_conv.ptr_) not involving conver- sions to pointers to private or protected or ambiguous classes --a qualification conversion [Note: a throw-expression which is an integral constant expression of integer type that evaluates to zero does not match a handler of pointer type; that is, the null pointer constant conversions (_conv.ptr_, _conv.mem_) do not apply. ] 4 [Example: class Matherr { /* ... */ virtual vf(); }; class Overflow: public Matherr { /* ... */ }; class Underflow: public Matherr { /* ... */ }; class Zerodivide: public Matherr { /* ... */ }; void f() { try { g(); } catch (Overflow oo) { // ... } catch (Matherr mm) { // ... } } Here, the Overflow handler will catch exceptions of type Overflow and the Matherr handler will catch exceptions of type Matherr and of all types publicly derived from Matherr including exceptions of type Underflow and Zerodivide. ] 5 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. 6 A ... in a handler's 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. 7 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. 8 An exception is considered handled upon entry to a handler. [Note: the stack will have been unwound at that point. ] 9 If no matching handler is found in a program, the function terminate() (_except.terminate_) is called; whether or not the stack is unwound before this call to terminate() is implementation-defined. 10Referring to any non-static member or base class of an object in the handler for a function-try-block of a constructor or destructor for that object results in undefined behavior. 11The fully constructed base classes and members of an object shall be destroyed before entering the handler of a function-try-block of a constructor or destructor for that object. 12The scope and lifetime of the parameters of a function or constructor extend into the handlers of a function-try-block. 13Exceptions thrown in destructors of objects with static storage dura- tion or in constructors of namespace-scope objects are not caught by a function-try-block on main(). 14If the handlers of a function-try-block contain a jump into the body of a constructor or destructor, the program is ill-formed. 15If a return statement appears in a handler of the function-try-block of a constructor, the program is ill-formed. 16The 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 han- dler for the function-try-block (_stmt.return_). Flowing off the end of a function-try-block is equivalent to a return with no value; this results in undefined behavior in a value-returning function (_stmt.return_). 17When the exception-declaration specifies a class type, a copy con- structor is used to initialize either the object declared in the exception-declaration or, if the exception-declaration does not spec- ify a name, a temporary object of that type. The object shall not have an abstract class type. The object is destroyed when the handler exits, after the destruction of any automatic objects initialized within the handler. The copy constructor and destructor shall be accessible in the context of the handler. If the copy constructor and destructor are implicitly declared (_class.copy_), such a use in the handler causes these functions to be implicitly defined; otherwise, the program shall provide a definition for these functions. 18If 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, then the optional name can be bound directly to the temporary object specified in a throw-expression causing the handler to be executed. The copy constructor and destructor associated with the object shall be acces- sible even when the temporary object is eliminated. +------- BEGIN BOX 1 -------+ The option of aliasing the handler variable with the temporary object is almost certainly wrong, and needs to be changed (given a ballot comment) in the next version. +------- END BOX 1 -------+ 19When the handler declares a non-constant object, any changes to that object will not affect the temporary object that was initialized by execution of the throw-expression. When the handler declares a refer- ence to a non-constant object, any changes to the referenced object are changes to the temporary object initialized when the throw-expres- sion was executed and will have effect should that object be rethrown. 15.4 Exception specifications [except.spec] 1 A function declaration lists exceptions that its function might directly or indirectly throw by using an exception-specification as a suffix of its declarator. 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, reference or pointer to member declaration or 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 --end example] A type denoted in an exception-specification shall not denote an incomplete type. A type denoted in an exception-specifica- tion shall not denote a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. +------- BEGIN BOX 2 -------+ Shouldn't the copy constructor be accessible for any (top-level) class type, so that it is possible to transform the exception specification into a function try block? +------- END BOX 2 -------+ 2 If any declaration of a function has an exception-specification, all declarations, including the definition and an explicit specialization, of that function shall have an exception-specification with the same set of type-ids. If any declaration of a pointer to function, refer- ence to function, or pointer to member function has an exception-spec- ification, all occurrences of that declaration shall have an excep- tion-specification with the same set of type-ids. In an explicit instantiation directive an exception-specification may be specified, but is not required. If an exception-specification is specified in an explicit instantiation directive, it shall have the same set of type- ids as other declarations of that function. A diagnostic is required only if the sets of type-ids are different within a single translation unit. 3 If a virtual function has an exception-specification, all declara- tions, including the definition, of any function that overrides 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 excep- tions, 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 to or initialized. [Example: class A { /* ... */ }; 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 } --end example] 4 In such an assignment or initialization, exception-specifications on return types and parameter types shall match exactly. In other assignments or initializations, exception-specifications shall match exactly. +------- BEGIN BOX 3 -------+ This was commented out in the typesetting document source. Why? 5 Calling a function through a declaration whose exception-specification allows other exceptions than those allowed by the exception-specifica- tion of the function's definition is ill-formed. No diagnostic is required. [San Diego motion 23] +------- END BOX 3 -------+ 6 Types shall not be defined in exception-specifications. 7 An exception-specification can include the same type more than once and can include classes that are related by inheritance, even though doing so is redundant. An exception-specification can also include the class std::bad_exception (_lib.bad.exception_). 8 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-specifi- cation of a function, the function allows exceptions of type Y* or 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-spec- ification. 9 Whenever an exception is thrown and the search for a handler (_except.handle_) encounters the outermost block of a function with an exception-specification, the function unexpected() is called (_except.unexpected_) if the exception-specification does not allow the exception. [Example: 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() } --end example] 10The function unexpected() may throw an exception that will satisfy the exception-specification for which it was invoked, and in this case the search for another handler will continue at the call of the function with this exception-specification (see _except.unexpected_), or it may call terminate(). 11An implementation shall not reject an expression merely because when executed it throws or might throw an exception that the containing function does not allow. [Example: extern void f() throw(X, Y); void g() throw(X) { f(); // OK } the call to f is well-formed even though when called, f might throw exception Y that g does not allow. ] 12A function with no exception-specification allows all exceptions. A function with an empty exception-specification, throw(), does not allow any exceptions. 13An exception-specification is not considered part of a function's type. 14An implicitly declared special member function (clause _special_) shall have an exception-specification. If f is an implicitly declared default constructor, copy constructor, destructor, or copy assignment operator, its implicit exception-specification specifies the type-id T if and only if T is allowed by the exception-specification of a func- tion directly invoked by f's implicitly definition; f shall allow all exceptions if any function it directly invokes allows all exceptions, and f shall allow no exceptions if every function it directly invokes allows no exceptions. [Example: struct A { A(); A(const A&) throw(); ~A() throw(X); }; struct B { B() throw(); B(const B&) throw(); ~B() throw(Y); }; struct D : public A, public B { // Implicit declaration of D::D(); // Implicit declaration of D::D(const D&) throw(); // Implicit declaration of D::~D() throw (X,Y); }; Furthermore, if A::~A() or B::~B() were virtual, D::~D() would not be as restrictive as that of A::~A, and the program would be ill-formed since a function that overrides a virtual function from a base class shall have an exception-specification at least as restrictive as that in the base class. ] 15.5 Special functions [except.special] 1 The exception handling mechanism relies on two functions, terminate() and unexpected(), for coping with errors related to the exception han- dling mechanism itself (_lib.support.exception_). 15.5.1 The terminate() function [except.terminate] 1 In the following situations exception handling must be abandoned for less subtle error handling techniques: --when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (_except.throw_), calls a user function that exits via an uncaught exception,1) --when the exception handling mechanism cannot find a handler for a thrown exception (_except.handle_), or --when the destruction of an object during stack unwinding (_except.ctor_) exits using an exception, or --when construction or destruction of a non-local object with static storage duration exits using an exception (_basic.start.init_), or --when execution of a function registered with atexit exits using an exception (_lib.support.start.term_), or _________________________ 1) For example, if the object being thrown is of a class with a copy constructor, terminate() will be called if that copy constructor exits with an exception during a throw. --when a throw-expression with no operand attempts to rethrow an exception and no exception is being handled (_except.throw_), or --when unexpected throws an exception which is not allowed by the pre- viously violated exception-specification, and std::bad_exception is not included in that exception-specification (_except.unexpected_), or --when the implementation's default unexpected_handler is called (_lib.unexpected.handler_) 2 In such cases, void terminate(); is called (_lib.exception.terminate_). [Note: in the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before terminate() is called. In all other situations, the stack shall not be unwound before terminate() is called. ] 15.5.2 The unexpected() function [except.unexpected] 1 If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function void unexpected(); is called (_lib.exception.unexpected_) immediately after completing the stack unwinding for the former function 2 The unexpected() function shall not return, but it can throw (or re- throw) an exception. If it throws a new exception which is allowed by the exception specification which previously was violated, then 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 then the following happens: if the exception-specification does not include the class std::bad_exception (_lib.bad.exception_) then the function ter- minate() is called, otherwise the thrown exception is replaced by an implementation-defined object of the type std::bad_exception and the search for another handler will continue at the call of the function whose exception-specification was violated. 3 Thus, an exception-specification guarantees that only the listed exceptions will be thrown. If the exception-specification includes the type std::bad_exception then any exception not on the list may be replaced by std::bad_exception within the function unexpected(). 15.5.3 The uncaught_exception() function [except.uncaught] 1 The function bool uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler (_lib.uncaught_). This includes stack unwinding. If the exception is rethrown (_except.throw_), uncaught_exception() returns true from the point of rethrow until the rethrown exception is caught again. 15.6 Exceptions and access [except.access] 1 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. 2 An object can be thrown if it can be copied and destroyed in the con- text of the function in which the throw-expression occurs.