exit to terminate the program completely. A tough way to handle aproblem if only because the destructors of local objects aren't activated.setjmp andlongjmp to enforce non-local exits. This mechanism implements a kindofgoto jump, allowing the program to continue at an outer level,skipping the intermediate levels which would have to be visited if a series ofreturns from nested functions would have been used.setjmp andlongjmp isn't frequently encountered inC++ (or even inC)programs, due to the fact that the program flow is completely disrupted.C++ offersexceptions as the preferred alternative to, e.g.,setjmp andlongjmp. Exceptions allowC++ programs to perform acontrollednon-local return, without the disadvantages oflongjmp andsetjmp.
Exceptions are the proper way to bail out of a situation which cannot behandled easily by a function itself, but which is not disastrous enough fora program to terminate completely. Also, exceptions provide a flexible layerof control between the short-rangereturn and the crudeexit.
In this chapter exceptions are covered. First an example is given of thedifferent impact exceptions and thesetjmp/longjmp combination have onprograms. This example is followed by a discussion of the formal aspectsof exceptions. In this part the guarantees our software should be ableto offer when confronted with exceptions are presented. Exceptions and theirguarantees have consequences for constructors and destructors. We'll encounterthese consequences at the end of this chapter.
throw statement. The keywordthrow, followed by an expression of a certain type, throws the expressionvalue as an exception. InC++ anything having value semantics may bethrown as an exception: anint, abool, astring, etc. However,there also exists astandard exception type (cf. section10.8) thatmay be used asbase class (cf. chapter13) when defining newexception types.try-block. The run-time support system ensures that all of theprogram's code is itself surrounded by aglobaltry block. Thus, everyexception generated by our code will always reach the boundary of at least onetry-block. A program terminates when an exception reachesthe boundary of the globaltry block, and when this happens destructors oflocal and global objects that were alive at the point where the exception wasgenerated are not called. This is not a desirable situation and therefore allexceptions should be generated within atry-block explicitly defined bythe program. Here is an example of a string exception thrown from within atry-block:try{ // any code can be defined here if (someConditionIsTrue) throw "this is the std::string exception"s; // any code can be defined here}catch: Immediately following thetry-block, one or morecatch-clauses must be defined. Acatch-clause consists of acatch-header defining the type of the exception it can catch followed by acompound statement defining what to do with the caught exception:catch (string const &msg){ // statements in which the caught string object are handled}Multiplecatch clauses may appear underneath each other, one for eachexception type that has to be caught. In general thecatch clauses mayappear in any order, but there are exceptions requiring a specific order. Toavoid confusion it's best to put acatch clause for the most generalexception last. At mostone exception clause will be activated.C++does not support aJava-stylefinally-clause activated aftercompleting a catch clause.
Outer andInner.First, anOuter object is defined inmain, and its memberOuter::fun is called. Then, inOuter::fun anInner object isdefined. Having defined theInner object, its memberInner::fun iscalled.
That's about it. The functionOuter::fun terminates callinginner's destructor. Then the program terminates, activatingouter's destructor. Here is the basic program:
#include <iostream> using namespace std; class Inner { public: Inner(); ~Inner(); void fun(); }; Inner::Inner() { cout << "Inner constructor\n"; } Inner::~Inner() { cout << "Inner destructor\n"; } void Inner::fun() { cout << "Inner fun\n"; } class Outer { public: Outer(); ~Outer(); void fun(); }; Outer::Outer() { cout << "Outer constructor\n"; } Outer::~Outer() { cout << "Outer destructor\n"; } void Outer::fun() { Inner in; cout << "Outer fun\n"; in.fun(); } int main() { Outer out; out.fun(); } /* Generated output: Outer constructor Inner constructor Outer fun Inner fun Inner destructor Outer destructor */ After compiling and running, the program's output is entirely as expected:the destructors are called in their correct order (reversing the callingsequence of the constructors).Now let's focus our attention on two variants in which we simulate a non-fataldisastrous event in theInner::fun function. This event must supposedly behandled nearmain's end.
We'll consider two variants. In the first variant the event is handled bysetjmp andlongjmp; in the second variant the event is handled usingC++'s exception mechanism.
jmp_buf jmpBuf used bysetjmp andlongjmp.The functionInner::fun callslongjmp, simulating a disastrousevent, to be handled nearmain's end. Inmain a target location forthe long jump is defined through the functionsetjmp.Setjmp's zeroreturn indicates the initialization of thejmp_buf variable, in which caseOuter::fun is called. This situation represents the `normal flow'.
The program's return value is zeroonly ifOuter::fun terminatesnormally. The program, however, is designed in such a way that this won'thappen:Inner::fun callslongjmp. As a result the execution flowreturns to thesetjmp function. In this case it doesnot return a zeroreturn value. Consequently, after callingInner::fun fromOuter::funmain'sif-statement is entered and the program terminates with returnvalue 1. Try to follow these steps when studying the following programsource, which is a direct modification of the basic program given in section10.2:
#include <iostream> #include <setjmp.h> #include <cstdlib> using namespace std; jmp_buf jmpBuf; class Inner { public: Inner(); ~Inner(); void fun(); }; Inner::Inner() { cout << "Inner constructor\n"; } void Inner::fun() { cout << "Inner fun\n"; longjmp(jmpBuf, 0); } Inner::~Inner() { cout << "Inner destructor\n"; } class Outer { public: Outer(); ~Outer(); void fun(); }; Outer::Outer() { cout << "Outer constructor\n"; } Outer::~Outer() { cout << "Outer destructor\n"; } void Outer::fun() { Inner in; cout << "Outer fun\n"; in.fun(); } int main() { Outer out; if (setjmp(jmpBuf) != 0) return 1; out.fun(); } /* Generated output: Outer constructor Inner constructor Outer fun Inner fun Outer destructor */ This program's output clearly shows thatinner's destructor is notcalled. This is a direct consequence of the non-local jump performed bylongjmp. Processing proceeds immediately from thelongjmp call insideInner::fun tosetjmp inmain. There, its return value is unequalzero, and the program terminates with return value 1. Because of the non-localjumpInner::~Inner is never executed: upon return tomain'ssetjmpthe existing stack is simply broken down disregarding any destructors waitingto be called.This example illustrates that the destructors of objects can easily be skippedwhenlongjmp andsetjmp are used andC++ programs should thereforeavoid those functions like the plague.
setjmp andlongjmp. Here is an example using exceptions. The program is once againderived from the basic program of section10.2: #include <iostream> using namespace std; class Inner { public: Inner(); ~Inner(); void fun(); }; Inner::Inner() { cout << "Inner constructor\n"; } Inner::~Inner() { cout << "Inner destructor\n"; } void Inner::fun() { cout << "Inner fun\n"; throw 1; cout << "This statement is not executed\n"; } class Outer { public: Outer(); ~Outer(); void fun(); }; Outer::Outer() { cout << "Outer constructor\n"; } Outer::~Outer() { cout << "Outer destructor\n"; } void Outer::fun() { Inner in; cout << "Outer fun\n"; in.fun(); } int main() { Outer out; try { out.fun(); } catch (int x) {} } /* Generated output: Outer constructor Inner constructor Outer fun Inner fun Inner destructor Outer destructor */Inner::fun now throws anint exception where alongjmp waspreviously used. Sincein.fun is called byout.fun, the exception isgenerated within thetry block surrounding theout.fun call. As anint value was thrown this value reappears in thecatch clause beyondthetry block.NowInner::fun terminates by throwing an exception instead of callinglongjmp. The exception is caught inmain, and the programterminates. Now we see thatinner's destructor is properly called. It isinteresting to note thatInner::fun's execution really terminates at thethrow statement: Thecout statement, placed just beyond thethrowstatement, isn't executed.
What did this example teach us?
return-statements, andwithout the need to terminate the program using blunt tools like the functionexit.setjmp andlongjmpdo distrupt the proper activation ofdestructors their use is strongly deprecated inC++.throw statements. Thethrow keyword isfollowed by an expression, defining the thrown exception value. Example: throw "Hello world"; // throws a char * throw 18; // throws an int throw string{ "hello" }; // throws a stringLocal objects cease to exist when a function terminates. This is nodifferent for exceptions.
Objects defined locally in functions are automatically destroyed onceexceptions thrown by these functions leave these functions. This also happensto objects thrown as exceptions. However, just before leaving the functioncontext the object is copied and it is this copy that eventually reaches theappropriatecatch clause.
The following examples illustrates this process.Object::fun defines a localObject toThrow, that isthrown as an exception. The exception is caughtinmain. But by then the object originally thrown doesn't exist anymore,andmain received a copy:
#include <iostream> #include <string> using namespace std; class Object { string d_name; public: Object(string name) : d_name(name) { cout << "Constructor of " << d_name << "\n"; } Object(Object const &other) : d_name(other.d_name + " (copy)") { cout << "Copy constructor for " << d_name << "\n"; } ~Object() { cout << "Destructor of " << d_name << "\n"; } void fun() { Object toThrow("'local object'"); cout << "Calling fun of " << d_name << "\n"; throw toThrow; } void hello() { cout << "Hello by " << d_name << "\n"; } }; int main() { Object out{ "'main object'" }; try { out.fun(); } catch (Object o) { cout << "Caught exception\n"; o.hello(); } }Object's copy constructor is special in that it defines its name asthe other object's name to which the string" (copy)" is appended. Thisallow us to monitor the construction and destruction of objects more closely.Object::fun generates an exception, and throws its locally definedobject. Just before throwing the exception the program has produced thefollowing output:Constructor of 'main object' Constructor of 'local object' Calling fun of 'main object'
When the exception is generated the next line of output is produced:
Copy constructor for 'local object' (copy)
The local object is passed tothrow where it is treated as a valueargument, creating a copy oftoThrow. This copy is thrown as theexception, and the localtoThrow object ceases to exist. The thrownexception is now caught by thecatch clause, defining anObject value parameter. Since this is avalue parameter yet anothercopy is created. Thus, the program writes the following text:
Destructor of 'local object' Copy constructor for 'local object' (copy) (copy)
Thecatch block now displays:
Caught exception
Following thiso'shello member is called, showing us that weindeed received acopy of the copy of the originaltoThrow object:
Hello by 'local object' (copy) (copy)
Then the program terminates and its remaining objects are nowdestroyed, reversing their order of creation:
Destructor of 'local object' (copy) (copy) Destructor of 'local object' (copy) Destructor of 'main object'
The copy created by thecatch clause clearly is superfluous. It can beavoided by defining objectreference parameters incatch clauses:`catch (Object &o)'. The program now produces the following output:
Constructor of 'main object' Constructor of 'local object' Calling fun of 'main object' Copy constructor for 'local object' (copy) Destructor of 'local object' Caught exception Hello by 'local object' (copy) Destructor of 'local object' (copy) Destructor of 'main object'
Only a single copy oftoThrow was created.
It's a bad idea to throw apointer to a locally definedobject. The pointer is thrown, but the object to which the pointer refersceases to exist once the exception is thrown. The catcher receives awild pointer. Bad news....
Let's summarize the above findings:
if (!parse(expressionBuffer)) // parsing failed throw "Syntax error in expression"; if (!lookup(variableName)) // variable not found throw "Variable not defined"; if (divisionByZero()) // unable to do division throw "Division by zero is not defined";
Where thesethrow statements are located is irrelevant: they may befound deeply nested inside the program, or at a more superficial level.Furthermore,functions may be used to generate the exception to bethrown. AnException object might support stream-like insertion operationsallowing us to do, e.g.,
if (!lookup(variableName)) throw Exception() << "Undefined variable '" << variableName << "';
In this situation an intermediate exception handler is called for. A thrownexception is first inspected at the middle level. If possible it is processedthere. If it is not possible to process the exception at the middle level, itis passed on, unaltered, to a more superficial level, where the really toughexceptions are handled.
By placing anemptythrow statement in the exceptionhandler's code the received exception is passed on to the next level thatmight be able to process that particular type of exception. Therethrownexception is never handled by one of its neighboring exception handlers; itis always transferred to an exception handler at a more superficial level.
In our server-client situation a function
initialExceptionHandler(string &exception)
could be designed to handle thestring exception. The received messageis inspected. If it's a simple message it's processed, otherwise the exceptionis passed on to an outer level. IninitialExceptionHandler'simplementation the emptythrow statement is used:
void initialExceptionHandler(string &exception) { if (!plainMessage(exception)) throw; handleTheMessage(exception); }Below (section10.5), the emptythrow statement is usedto pass on the exception received by acatch-block. Therefore, a functionlikeinitialExceptionHandler can be used for a variety of thrownexceptions, as long as their types matchinitialExceptionHandler'sparameter, which is a string.
The next example jumps slightly ahead, using some of the topics covered inchapter14. The example may be skipped, though, withoutloss of continuity.
A basic exception handling class can be constructed from which specificexception types are derived. Suppose we have a classException, having amember functionExceptionType Exception::severity. This member functiontells us (little wonder!) the severity of a thrown exception. It might beInfo, Notice, Warning, Error orFatal. The information contained inthe exception depends on its severity and is processed by a functionhandle. In addition, all exceptions support a member function liketextMsg, returning textual information about the exception in astring.
By defining a polymorphic functionhandle it can be made to behavedifferently, depending on the nature of a thrown exception, when calledfrom a basicException pointer or reference.
In this case, a program may throw any of these five exception types. Assumingthat the classesMessage andWarning were derived from the classException, then thehandle function matching the exception type willautomatically be called by the following exception catcher:
// catch(Exception &ex) { cout << e.textMsg() << '\n'; if ( ex.severity() != ExceptionType::Warning && ex.severity() != ExceptionType::Message ) throw; // Pass on other types of Exceptions ex.handle(); // Process a message or a warning }Now anywhere in thetry block preceding the exception handlerException objects or objects of one of its derived classes may bethrown. All those exceptions will be caught by the above handler. E.g.,
throw Info{}; throw Warning{}; throw Notice{}; throw Error{}; throw Fatal{};try-block surroundsthrow statements. Remember that a program isalways surrounded by a globaltry block, sothrow statements mayappear anywhere in your code. More often, though,throw statements areused in function bodies and such functions may be called from withintryblocks.Atry block is defined by the keywordtry followed by a compoundstatement. This block, in turn,must be followed by at least onecatch handler:
try { // any statements here } catch(...) // at least one catch clause here {}Try-blocks are commonly nested, creating exceptionlevels. Forexample,main's code is surrounded by atry-block, forming an outerlevel handling exceptions. Withinmain'stry-block functions arecalled which may also containtry-blocks, forming the next exceptionlevel. As we have seen (section10.3.1), exceptions thrown ininner leveltry-blocks may or may not be processed at that level. Byplacing an emptythrow statement in an exception handler, thethrown exception is passed on to the next (outer) level.
catch clause consists of the keywordcatch followed by a parameterlist defining one parameter specifying type and (parameter) name of theexception caught by that particularcatch handler. This name may then beused as a variable in the compound statement following thecatch clause.Example: catch (string &message) { // code to handle the message }Primitive types and objects may be thrown as exceptions. It's a bad ideato throw a pointer or reference to a local object, but a pointer to adynamically allocated object may be thrown if the exception handlerdeletes the allocated memory to prevent amemory leak. Nevertheless,throwing such a pointer is dangerous as the exception handler won't be able todistinguish dynamically allocated memory from non-dynamically allocatedmemory, as illustrated by the next example:
try { static int x; int *xp = &x; if (condition1) throw xp; xp = new int(0); if (condition2) throw xp; } catch (int *ptr) { // delete ptr or not? }Close attention should be paid to the nature of the parameter of theexception handler, to make sure that when pointers to dynamically allocatedmemory are thrown the memory is returned once the handler has processedthe pointer. In general pointers should not be thrown as exceptions. Ifdynamically allocated memory must be passed to an exception handler then thepointer should be wrapped in a smart pointer, likeunique_ptr orshared_ptr (cf. sections18.3 and18.4).
Multiplecatch handlers may follow atry block, each handlerdefining its own exception type. Theorderof the exception handlers is important. When an exception is thrown, the firstexception handler matching the type of the thrown exception is used andremaining exception handlers are ignored. Eventually at most one exceptionhandler following atry-block is activated. Normally this is of noconcern as each exception has its own unique type.
Example: if exception handlers are defined forchar *s andvoid *sthen NTBSs are caught by the former handler. Note that achar* can also be considered avoid *, but the exception type matchingprocedure is smart enough to use thechar * handler with the thrownNTBS. Handlers should be designed very type specific to catch thecorrespondingly typed exception. For example,int-exceptions are notcaught bydouble-catchers,char-exceptions are not caught byint-catchers. Here is a little example illustrating that the order of thecatchers is not important for types not having any hierarchal relationship toeach other (i.e.,int is not derived fromdouble;string is notderived from an NTBS):
#include <iostream>using namespace std;int main(){ while (true) { try { string s; cout << "Enter a,c,i,s for ascii-z, char, int, string " "exception\n"; getline(cin, s); switch (s[0]) { case 'a': throw "ascii-z"; case 'c': throw 'c'; case 'i': throw 12; case 's': throw string{}; } } catch (string const &) { cout << "string caught\n"; } catch (char const *) { cout << "ASCII-Z string caught\n"; } catch (double) { cout << "isn't caught at all\n"; } catch (int) { cout << "int caught\n"; } catch (char) { cout << "char caught\n"; } }} Rather than defining specific exception handlers a specific class can bedesigned whose objects contain information about the exception. Such anapproach was mentioned earlier, in section10.3.1. Using thisapproach, there's only one handler required, since weknow we don't throwother types of exceptions: try { // code throws only Exception objects } catch (Exception &ex) { ex.handle(); }When the code of an exception handler has been processed, execution continuesbeyond the last exception handler directly following the matchingtry-block (assuming the handler doesn't itself use flow control statements(likereturn orthrow) to break the default flow of execution). Thefollowing cases can be distinguished:
try-block no exceptionhandler is activated, and execution continues from the last statement inthetry-block to the first statement beyond the lastcatch-block.try-block but neitherthe current level nor another level contains an appropriate exception handler,the program's default exception handler is called, aborting the program.try-block and an appropriateexception handler is available, then the code of that exception handler isexecuted. Following that, the program's execution continues at the firststatement beyond the lastcatch-block.try block following an executedthrow-statement are ignored. However, objects that were successfullyconstructed within thetry block before executing thethrow statementare destroyed before any exception handler's code is executed.try block.An intermediate type of exception handling may be implemented using thedefault exception handler, which must be (due to the hierarchal nature ofexception catchers, discussed in section10.5) placed beyondall other, more specific exception handlers.
This default exception handler cannot determine the actual type of the thrownexception and cannot determine the exception's value but it may execute somestatements, and thus do some default processing. Moreover, the caughtexception is not lost, and the default exception handler may use the emptythrow statement (see section10.3.1) to pass the exception on toan outer level, where it's actually processed. Here is an example showingthis use of a default exception handler:
#include <iostream> using namespace std; int main() { try { try { throw 12.25; // no specific handler for doubles } catch (int value) { cout << "Inner level: caught int\n"; } catch (...) { cout << "Inner level: generic handling of exceptions\n"; throw; } } catch(double d) { cout << "Outer level may use the thrown double: " << d << '\n'; } } /* Generated output: Inner level: generic handling of exceptions Outer level may use the thrown double: 12.25 */ The program's output illustrates that an emptythrow statement in adefault exception handler throws the received exception to the next (outer)level of exception catchers, keeping type and value of the thrown exception.Thus, basic or generic exception handling can be accomplished at an innerlevel, while specific handling, based on the type of the thrown expression,can be provided at an outer level. Additionally, particularly inmulti-threaded programs (cf. chapter20), thrown exceptions can betransferred between threads after convertingstd::exception objects tostd::exception_ptr objects. This proceduce can even be used from insidethe default catcher. Refer to section10.9.4 for further coverage of theclassstd::exception_ptr.
swap and destructors may not throw exceptions.Functions that may not throw exceptions can be declared and defined byspecifying thenoexcept keyword (see section10.9 for examplesof function declarations specifyingnoexcept).
When usingnoexept there's a slight run-time overhead penalty because thefunction needs an over-alltry-catch block catching any exception thatmight be thrown by its (called) code. When an exception is caught (violatingthenoexcept specification) then thecatch clause callsstd::terminate, ending the program.
In addition to using a plainnoexcept, it can also be given an argumentthat is evaluated compile-time (e.g.,void fun() noexcept(sizeof(int) ==4)): if the evaluation returnstrue then thenoexcept requirement isused; if the evaluation returnsfalse, then thenoexcept requirementis ignored. Examples of this advanced use ofnoexcept are provided insection23.8.
ios::exceptions member function. This function has two overloadedversions:ios::iostate exceptions():void exceptions(ios::iostate state)state is observed.In the I/O library, exceptions are objects of the classios::failure, derived fromios::exception. Astd::string const &message may be specified whendefining afailure object. Its message may then be retrieved using itsvirtual char const *what() const member.
Exceptions should be used in exceptional circumstances. Therefore, wethink it is questionable to have stream objects throw exceptions for fairlynormal situations likeEOF. Using exceptions to handle input errorsmight be defensible (e.g., in situations where input errors should not occurand imply a corrupted file) but often aborting the program with an appropriateerror message would probably be the more appropriate action. As an exampleconsider the following interactive program using exceptions to catch incorrectinput:
#include <iostream> #include <climits> using namespace::std; int main() { cin.exceptions(ios::failbit); // throw exception on fail while (true) { try { cout << "enter a number: "; int value; cin >> value; cout << "you entered " << value << '\n'; } catch (ios::failure const &problem) { cout << problem.what() << '\n'; cin.clear(); cin.ignore(INT_MAX, '\n'); // ignore the faulty line } } }By default, exceptions raised from withinostream objects are caught bythese objects, which set theirios::badbit as a result. See also theparagraph on this issue in section14.8.
<stdexcept> header file must be included.All of thesestandard exceptions are class types by themselves, but also offerall facilities of thestd::exception class and objectsof the standard exception classes may also be considered objects of thestd::exception class.
Thestd::exception class offers the member
char const *what() const;
describing in a short textual message the nature of theexception.
C++ defines the following standard exception classes:
std::bad_alloc (this requires the<new> header file): thrown whenoperator new fails;std::bad_array_new_length (this requires the<new> header file): thrown when an illegal array size is requested when usingnew Type[...]. Illegal sizes are negative values, values that exceed an implementation defined maximum, the number of initializer clauses exceeds the specified number of array elements (e.g.,new int[2]{ 1, 2, 3 });std::bad_cast (this requires the<typeinfo> header file): thrown in the context ofpolymorphism (see section14.6.1);std::bad_exception (this requires the<exception> header file): thrown when a function tries to generate another type of exception than declared in itsfunction throw list;std::bad_typeid (this requires the<typeinfo> header file): also thrown in the context ofpolymorphism (see section14.6.2);All additional exception classes were derived fromstd::exception. Theconstructors of all these additional classes acceptstd::string const &arguments summarizing the reason for the exception (retrieved by theexception::what member). The additionally defined exception classes are:
std::domain_error: a (mathematical) domain error isdetected;std::invalid_argument: the argument of a functionhas an invalid value;std::length_error: thrown when an object would haveexceeded its maximum permitted length;std::logic_error: a logic error should be thrown when aproblem is detected in the internal logic of the program. Example: a functionlikeC'sprintf is called with more arguments than there are formatspecifiers in its format string;std::out_of_range: thrown when an argument exceeds itspermitted range. Example: thrown byat members when their arguments exceedthe range of admissible index values;std::overflow_error: an overflow error should bethrown when an arithmetic overflow is detected. Example: dividing avalue by a very small value;std::range_error: a range error should be thrown whenan internal computation results in a value exceeding a permissible range;std::runtime_error: a runtime error should be thrownwhen a problem is encountered that can only be detected while the program isbeing executed. Example: a non-integral is entered when the program's inputexpects an integral value.std::underflow_error: an underflow error should bethrown when an arithmetic underflow is detected. Example: dividing a verysmall value by a very large value.std::tx_exception<Type>: derived fromstd::runtime_error. This exception can be thrown from anatomic_cancelcompound statement (cf. section20.14) to undo statements executed sofar.Current practice in the C++ community is to throw exceptions only inexceptional situations. In that respect C++'s philosophy about usingexceptions differs markedly from the way exceptions are used in, e.g., Java,where exceptions are often encountered in situations C++ doesn't considerexceptional. Another common practice is to follow a `conceptual' style whendesigning software. A nice characteristic of exceptions is that exceptions canbe thrown at a point where your source shows what's happening: throwing anstd::out_of_range exception is nice for the software maintainer, asthe reason for the exception is immediately recognized.
At the catch-clause the semantical context usually isn't very relevant anymoreand by catching a std::exception and showing itswhat() content theprogram'suser is informed about what happened.
But throwing values of other types can also be useful. What about a situationwhere you want to throw an exception and catch it at some shallow level? Inbetween there may be various levels of software provided by external softwarelibraries over which the software engineer has no control. At those levelsexceptions (std::exceptions) could be generated too, and those exceptionsmight also be caught by the library's code. When throwing a standard exceptiontype it may be hard to convince yourself that that exception isn't caught bythe externally provided software. Assuming that no catch-alls are used (i.e.,catch (...)) then throwing an exception from thestd::exceptionfamily might not be a very good idea. In such cases throwing a value from asimple, maybe empty,enum works fine:
enum HorribleEvent {}; ... at some deep level: throw HorribleEvent{}; ... at some shallow level: catch (HorribleEvent hs) { ... }Other examples can easily be found: design a class holding a message andan error (exit) code: where necessary throw an object of that class, catch itin the catch clause of main's try block and you can be sure that all objectsdefined at intermediate levels are neatly destroyed, and at the end you showthe error message and return the exit code embedded in your non-exceptionobject.
So, the advice is to usestd::exception types when available, andclearly do the required job. But if an exception is used to simply bail outof an unpleasant situation, or if there's a chance that externally providedcode might catchstd:exceptions then consider throwing objects or valuesof other types.
std::system_error is derived fromstd::runtime_error, which in turn is derived fromstd::exceptionBefore using the classsystem_error or related classes the<system_error> header file must be included.
System_error exceptions can be thrown when errors occur havingassociated (system)error values. Such errors are typically associatedwith low-level (like operating system) functions, but other types of errors(e.g., bad user input, non-existing requests) can also be handled.
In addition to error codes (cf. section4.3.1) and error categories(covered below) errorconditions are distinguished. Errorconditions specify platform independent types of errors like syntax errors ornon-existing requests.
When constructingsystem_error objects error codes and error categoriesmay be specified. First we'll look at the classeserror_condition anderror_category, thensystem_error itself is covered in more detail.
Figure9 illustrates how the various components interact.

As shown in figure9 the classerror_category uses the classerror_condition and the classerror_condition uses the classerror_category. As a consequence of this circular dependency between thesetwo classes these classes should be approached as one single class:when coveringerror_category the classerror_condition should be knownand vice versa. This circular dependency among these classes is unfortunateand an example of bad class design.
Assystem_error is eventually derived fromexception it offers thestandardwhat member. It also contains anerror_code.
In POSIX systems theerrno variable is associated with many, often rathercryptic, symbols. The predefinedenum class errc attempts to provideintuitively more appealing symbols. Since its symbols are defined in astrongly typed enumeration, they cannot directly be used when defining amatchingerror_code. Instead, amake_error_code function convertsenum class errc values and values of newly definederror code enumerations (calledErrorCodeEnum below) toerror_codeobjects.
Theenum class errc defined in thestd namespace defines symbols whosevalues are equal to the traditional error code values used byC but describe the errors in a less cryptic way. E.g.,
enum class errc { address_family_not_supported, // EAFNOSUPPORT address_in_use, // EADDRINUSE address_not_available, // EADDRNOTAVAIL already_connected, // EISCONN argument_list_too_long, // E2BIG argument_out_of_domain, // EDOM bad_address, // EFAULT ... };Values ofErrorCodeEnums can be passed to matchingmake_error_codefunctions. Defining your ownErrorCodeEnum enumeration is covered insection23.7.
Now that the general outline has been presented, it's time to have a closerlook at the various components shown in figure9.
std::error_category identify sources of sets of errorcodes. New error categories for new error code enumerations can also bedefined (cf. section23.7).Error categories are designed assingletons: only one object of each classcan exist. Because of thiserror_categories are equal when the addressesoferror_category objects are equal. Error category objects are returnedby functions (see below) or by staticinstance() members of error categoryclasses.
Error category classes define several members. Most are declaredvirtual(cf. chapter14), meaning that those members may be redefinedin error category classes we ourselves design:
virtual error_condition default_error_condition(int ev) const noexcept:error_condition object (cf. section10.9.2) initialized with error valueev and the current (i.e.,*this)error_category;virtual bool equivalent(error_code const &code, int condition) const noexcept:true if the equivalence between the error condition that is associated with theerror_code object and theerror_condition_enum value that is specified (as anint value) as the function's second argument could be establisted;virtual bool equivalent(int ev, error_condition const &condition) const noexcept:true if the equivalence of anerror_condition object that is constructed from theErrorConditionEnum value that is associated with theErrorCategoryEnum value that was passed (asint) to the function and theerror_condition object that was passed to the function as its second argument could be established;virtual string message(int ev) const:ev, which should be a (cast toint) value of the category's error condition enumeration;virtual char const *name() const noexcept:generic);bool operator<(error_category const &rhs) const noexcept:less<const error_category*>()(this, &rhs).The functions returning predefined error categories are:
error_category const &generic_category() noexcept:error_category object. The returned object'sname member returns a pointer to the string"generic";error_category const &system_category() noexcept:error_category object: it is used for errors reported by the operating system. The object'sname member returns a pointer to the string"system";error_category const &iostream_category() noexcept:error_category object: it is used for errors reported by stream objects. The object'sname member returns a pointer to the string"iostream";error_category const &future_category() noexcept:error_category object: it is used for errors reported by `future' objects (cf. section20.8). The object'sname member returns a pointer to the string"future";Error_condition objects contain information about`higher level' types of errors. They are supposed to be platform independentlike syntax errors or non-existing requests.Error condition objects are returned by the memberdefault_error_conditionof the classeserror_code anderror_category, and they are returned bythe functionstd::error_conditionmake_error_condition(ErrorConditionEnum ec). The type nameErrorConditionEnum is a formal name for anenum class that enumeratesthe `higher level' error types. Theerror_condition objects returned bymake_error_condition are initialized withec and theerror_category that uses theErrorConditionEnum. Defining your ownErrorConditionEnum is covered in section23.7.
Constructors:
error_condition() noexcept:system_category error category;error_condition(int ec, error_category const &cat) noexcept:ec and error categorycat. It is the responsibility of the caller to ensure thatec represents a (cast toint) value ofcat's error condition enumeration;error_condition(ErrorConditionEnum value) noexcept:template <class ErrorConditionEnum>. It initializes the object with the return value ofmake_error_condition(value);Members:
ErrorConditionEnum are available;void assign(int val, error_category const &cat):error_category const &category() const noexcept:void clear() noexcept:generic_category;string message() const:category().message(value());explicit operator bool() const noexcept:true ifvalue() returns a non-zero value (so its semantic meaning is `the object represents an error');int value() const noexcept:Twoerror_condition objects can be compared for (in)equality, and can beordered usingoperator<. Ordering is pointless if the two objects refer todifferent error categories. If the categories of two objects are different they are considered different.
System_error objects can be constructed fromerror_codes or from error values (ints) and matching error category objects, optionallyfollowed by a standard textual description of the nature of the encounterederror.Here is the class's public interface:
class system_error: public runtime_error { public: system_error(error_code ec); system_error(error_code ec, string const &what_arg); system_error(error_code ec, char const *what_arg); system_error(int ev, error_category const &ecat); system_error(int ev, error_category const &ecat, char const *what_arg); system_error(int ev, error_category const &ecat, string const &what_arg); error_code const &code() const noexcept; char const *what() const noexcept; }Theev values often are the values of theerrno variable as setupon failure by system level functions likechmod(2).
Note that the first three constructors shown in the interface receive anerror_code object as their first arguments. As one of theerror_codeconstructors also expects anint and anderror_category argument,the second set of three constructors could also be used instead of the firstset of three constructors. E.g.,
system_error(errno, system_category(), "context of the error"); // identical to: system_error(error_code(errno, system_category()), "context of the error");
The second set of three constructors are primarily used when an existingfunction already returns anerror_code. E.g.,
system_error(make_error_code(errc::bad_address), "context of the error"); // or maybe: system_error(make_error_code(static_cast<errc>(errno)), "context of the error");
In addition to the standardwhat member, thesystem_error class alsooffers a membercode returning a const reference to the exception's errorcode.
The NTBS returned bysystem_error's what member may be formatted by asystem_error object:
what_arg + ": " + code().message()
Note that, althoughsystem_error was derived fromruntime_error,you'll lose thecode member when catching astd::exception object. Ofcourse, downcasting is possible, but that's a stopgap. Therefore, if asystem_error is thrown, a matchingcatch(system_error const &) clausemust be provided to retrieve the value returned by thecode member. This,and the rather complex organization of the classes that are involved whenusingsystem_error result in a very complex, and hard to generalizeexception handling. In essence, what you obtain at the cost of highcomplexity is a facility for categorizingint orenum errorvalues. Additional coverage of the involved complexities is provided inchapter23, in particular section23.7(for a flexible alternative, see the classFBB::Exception in the author'sBobcat library).
std::current_exception), and access to any exceptioncan be standardized usingstd::make_exception_ptr.These functions expect or use objects of the classstd::exception_ptr, and in this section we take a closerlook at tha class.The classexception_ptr's default constructor initializes it to anull-pointer. In the following code snippet the variableisNull is set totrue:
std::exception_ptr obj; bool isNull = obj == nullptr && obj == 0;
The classexception_ptr provides copy and move constructors as well ascopy and move assignment operators.
Twoexception_ptr objects can be compared for equality. They are equalif they refer to the same exception. Move assignment transfers the exceptionreferred to by the right-hand side operand to the left-hand side operand, andturns the right-hand side operand into a null pointer.
There is no published method directly retrieving the exception to which anexception_ptr object refers. However, there are some free functionsconstructing or handlingexception_ptr objects:
std::exception_ptr std::current_exception() noexcept:exception_ptr object is returned referring to the currently handled exception (or a copy of the currently handled exception, or a default constructedexception_ptr object if no current exception is available). This function can also be called when a default exception catcher is used.The exception referred to bycurrent_exception does not have to be an object of the classstd::exception. Any type of object or value thrown as an exception is retrieved as anexception_ptr bycurrent_exception. The exception referred to by anexception_ptr object remains valid for at least as long as there exists anexception_ptr object that refers to it. Callingcurrent_exception twice in a row then the two returnedexception_ptr objects may or may not refer to the same exception object.
std::exception_ptr make_exception_ptr(Type value) noexcept:exception_ptr from a value of any type which is passed as its argument.Type does not necessarily have to be astd::exception but can be anything that can be thrown as an exception: anint, astd::string, astd::exception, you name it. Here are some examples, showing how values of different types can be passed as arguments tomake_exception_ptr
auto ptr = make_exception_ptr(exception()); ptr = make_exception("hello world"s); ptr = make_exception(12);void std::rethrow_exception(exception_ptr obj):obj refers is thrown. Note:obj cannot be anullptr.Since exceptions may be generated from within allC++ functions,exceptions may be generated in many situations. Not all of these situationsare immediately and intuitively recognized as situations where exceptions canbe thrown. Consider the following function and ask yourself at which pointsexceptions may be thrown:
void fun() { X x; cout << x; X *xp = new X{ x }; cout << (x + *xp); delete xp; }If it can be assumed thatcout as used above does not throw anexception there are at least 13 opportunities for exceptions to be thrown:
X x: the default constructor could throw an exception (#1)cout << x: the overloaded insertion operator could throw anexception (#2), but its rhs argument might not be anX but, e.g., anint, and soX::operator int() const could be called which offers yetanother opportunity for an exception (#3).*xp = new X{ x }: the copy constructor may throw an exception(#4) and operator new (#5a) too. But did you realize that this latterexception might not be thrown from::new, but from, e.g.,X's ownoverload ofoperator new? (#5b)cout << (x + *xp): we might be seduced into thinking that twoX objects are added. But it doesn't have to be that way. A separate classY might exist andX may have a conversion operatoroperator Y() const,andoperator+(Y const &lhs, X const &rhs), operator+(X const &lhs, Y const&rhs), andoperator+(X const &lhs, X const &rhs) might all exist. So, ifthe conversion operator exists, then depending on the kind of overload ofoperator+ that is defined either the addition's left-hand side operand(#6), right-hand side operand (#7), oroperator+ itself (#8) may throw anexception. The resulting value may again be of any type and so the overloadedcout << return-type-of-operator+ operator may throw an exception(#9). Sinceoperator+ returns a temporary object it is destroyed shortlyafter its use.X's destructorcould throw an exception (#10).delete xp: wheneveroperator new is overloadedoperatordelete should be overloaded as well and may throw an exception (#11). And ofcourse,X's destructor might again throw an exception (#12).}: when the function terminates the localx object isdestroyed: again an exception could be thrown (#13).How can we expect to create working programs when exceptions might be thrownin so many situations?
Exceptions may be generated in a great many situations, but seriousproblems are prevented when we're able to provide at least one of thefollowingexception guarantees:
void allocator(X **xDest, Y **yDest) { X *xp = 0; // non-throwing preamble Y *yp = 0; try // this part might throw { xp = new X[nX]; // alternatively: allocate one object yp = new Y[nY]; } catch(...) { delete xp; throw; } delete[] *xDest; // non-throwing postamble *xDest = xp; delete[] *yDest; *yDest = yp; }In the pre-try code the pointers to receive the addresses returned by theoperatornew calls are initialized to 0. Since the catch handler must beable to return allocated memory they must be available outside of thetryblock. If the allocation succeeds the memory pointed to by the destinationpointers is returned and then the pointers are given new values.
Allocation and or initialization might fail. If allocation failsnewthrows astd::bad_alloc exception and the catch handlersimply deletes 0-pointers which is OK.
If allocation succeeds but the construction of (some) of the objects failsby throwing an exception then the following isguaranteed to happen:
Consequently, there is no memory leak whennew fails. Inside the abovetry blocknew X may fail: this does not affect the 0-pointersand so the catch handler merely deletes 0 pointers. Whennew Y failsxp points to allocated memory and so it must be returned. This happensinside the catch handler. The final pointer (here:yp) will only beunequal zero whennew Y properly completes, so there's no need for thecatch handler to return the memory pointed at byyp.
Class &operator=(Class const &other) { Class tmp(other); swap(tmp); return *this; }The copy construction might throw an exception, but this keeps the currentobject's state intact. If the copy construction succeedsswap swaps thecurrent object's content withtmp's content and returns a reference tothe current object. For this to succeed it must be guaranteed thatswapwon't throw an exception. Returning a reference (or a value of a primitivedata type) is also guaranteed not to throw exceptions. The canonical form ofthe overloaded assignment operator therefore meets the requirements of thestrong guarantee.
Some rules of thumb were formulated that relate to thestrong guarantee (cf.Sutter, H.,Exceptional C++, Addison-Wesley, 2000). E.g.,
The canonical assignment operator is a good example of the first rule ofthumb. Another example is found in classes storing objects. Consider a classPersonDb storing multiplePerson objects. Such a class might offer amembervoid add(Person const &next). A plain implementation of thisfunction (merely intended to show the application of the first rule of thumb,but otherwise completely disregarding efficiency considerations) might be:
Person *PersonDb::newAppend(Person const &next) { Person *tmp = 0; try { tmp = new Person[d_size + 1]; for (size_t idx = 0; idx < d_size; ++idx) tmp[idx] = d_data[idx]; tmp[d_size] = next; return tmp; } catch (...) { delete[] tmp; throw; } } void PersonDb::add(Person const &next) { Person *tmp = newAppend(next); delete[] d_data; d_data = tmp; ++d_size; }The (private)newAppend member's task is to create a copy of thecurrently allocatedPerson objects, including the data of the nextPerson object. Itscatch handler catches any exception that might bethrown during the allocation or copy process and returns all memoryallocated so far, rethrowing the exception at the end. The function isexception neutral as it propagates all its exceptions to its caller. Thefunction also doesn't modify thePersonDb object's data, so it meets thestrong exception guarantee. Returning fromnewAppend the memberaddmay now modify its data. Its existing data are returned and itsd_datapointer is made to point to the newly created array ofPersonobjects. Finally itsd_size is incremented. As these three steps don'tthrow exceptionsadd too meets the strong guarantee.
The second rule of thumb (member functions modifying their object's datashould not return original (contained) objects by value) may be illustratedusing a memberPersonDb::erase(size_t idx). Here is an implementationattempting to return the originald_data[idx] object:
Person PersonData::erase(size_t idx) { if (idx >= d_size) throw "Array bounds exceeded"s; Person ret(d_data[idx]); Person *tmp = copyAllBut(idx); delete[] d_data; d_data = tmp; --d_size; return ret; }Although copy elision usually prevents the use of the copy constructorwhen returningret, this is not guaranteed to happen. Furthermore, a copyconstructormay throw an exception. If that happens the function hasirrevocably mutated thePersonDb's data, thus losing the strong guarantee.
Rather than returningd_data[idx] by value it might be assigned to anexternalPerson object before mutatingPersonDb's data:
void PersonData::erase(Person *dest, size_t idx) { if (idx >= d_size) throw "Array bounds exceeded"s; *dest = d_data[idx]; Person *tmp = copyAllBut(idx); delete[] d_data; d_data = tmp; --d_size; }This modification works, but changes the original assignment of creating amember returning the original object. However, both functions suffer from atask overload as they modifyPersonDb's data and also return an originalobject. In situations like these theone-function-one-responsibilityrule of thumb should be kept in mind: a function should have a single, welldefined responsibility.
The preferred approach is to retrievePersonDb's objects using a memberlikePerson const &at(size_t idx) const and to erase an object using amember likevoid PersonData::erase(size_t idx).
swap function. Consider once again the canonicaloverloaded assignment operator: Class &operator=(Class const &other) { Class tmp(other); swap(tmp); return *this; }Ifswap were allowed to throw exceptions then it would most likelyleave the current object in a partially swapped state. As a result the currentobject's state would most likely have been changed. Astmp has beendestroyed by the time a catch handler receives the thrown exception it becomesvery difficult (as in: impossible) to retrieve the object's originalstate. Losing the strong guarantee as a consequence.
Theswap function must therefore offer the nothrow guarantee. It musthave been designed as if using the following prototype (see also section23.8):
void Class::swap(Class &other) noexcept;
Likewise,operator delete andoperator delete[] offer the nothrowguarantee, and according to theC++ standard destructors may themselvesnot throw exceptions (if they do their behavior is formally undefined, seealso section10.12 below).
Since theC programming language does not define the exception conceptfunctions from the standardC library offer the nothrow guaranteeby implication. This allowed us to define the genericswap function insection9.6 usingmemcpy.
Operations on primitive types offer the nothrow guarantee. Pointers may bereassigned, references may be returned etc. etc. without having to worry aboutexceptions that might be thrown.
try block does not solve the problem. The exception bythen has left the constructor and the object we intended to construct isn'tvisible anymore.Using a nestedtry block is illustrated in the next example, wheremain defines an object of classPersonDb. Assuming thatPersonDb's constructor throws an exception, there is no way we can accessthe resources that might have been allocated byPersonDb's constructorfrom the catch handler as thepdb object is out of scope:
int main(int argc, char **argv) { try { PersonDb pdb{ argc, argv }; // may throw exceptions ... // main()'s other code } catch(...) // and/or other handlers { ... // pdb is inaccessible from here } }Although all objects and variables defined inside atry block areinaccessible from its associated catch handlers, object data members wereavailable before starting thetry block and so they may be accessed from acatch handler. In the following example the catch handler inPersonDb's constructor is able to access itsd_data member:
PersonDb::PersonDb(int argc, char **argv) : d_data(0), d_size(0) { try { initialize(argc, argv); } catch(...) { // d_data, d_size: accessible } }Unfortunately, this does not help us much. Theinitialize member isunable to reassignd_data andd_size ifPersonDb const pdbwas defined; theinitialize member should at least offer the basicexception guarantee and return any resources it has acquired beforeterminating due to a thrown exception; and althoughd_data andd_sizeoffer the nothrow guarantee as they are of primitive data types a class typedata member might throw an exception, possibly resulting in violation of thebasic guarantee.
In the next implementation ofPersonDb assume that constructorreceives a pointer to an already allocated block ofPerson objects. ThePersonDb object takes ownership of the allocated memory and it istherefore responsible for the allocated memory's eventual destruction.Moreover,d_data andd_size are also used by a composed objectPersonDbSupport, having a constructor expecting aPerson const * andsize_t argument. Our next implementation may then look something likethis:
PersonDb::PersonDb(Person *pData, size_t size) : d_data(pData), d_size(size), d_support(d_data, d_size) { // no further actions }This setup allows us to define aPersonDb const &pdb. Unfortunately,PersonDb cannot offer the basic guarantee. IfPersonDbSupport'sconstructor throws an exception it isn't caught althoughd_data alreadypoints to allocated memory.
Thefunction try block offers a solution for this problem. A functiontry block consists of atry block and its associated handlers. Thefunctiontry block startsimmediately after the function header, andits block defines the function body. With constructors base class and datamember initializers may be placed between thetry keyword and the openingcurly brace. Here is our final implementation ofPersonDb, now offeringthe basic guarantee:
PersonDb::PersonDb(Person *pData, size_t size) try : d_data(pData), d_size(size), d_support(d_data, d_size) {} catch (...) { delete[] d_data; }Let's have a look at a stripped-down example. A constructor defines afunction try block. The exception thrown by theThrow object is initiallycaught by the object itself. Then it is rethrown. The surroundingComposer's constructor also defines a function try block,Throw'srethrown exception is properly caught byComposer's exception handler,even though the exception was generated from within its member initializerlist:
#include <iostream> class Throw { public: Throw(int value) try { throw value; } catch(...) { std::cout << "Throw's exception handled locally by Throw()\n"; throw; } }; class Composer { Throw d_t; public: Composer() try // NOTE: try precedes initializer list : d_t(5) {} catch(...) { std::cout << "Composer() caught exception as well\n"; } }; int main() { Composer c; }When running this example, we're in for a nasty surprise: the program runsand then breaks with anabort exception. Here is the output it produces,the last two lines being added by the system's final catch-all handler,catching all remaining uncaught exceptions:
Throw's exception handled locally by Throw() Composer() caught exception as well terminate called after throwing an instance of 'int' Abort
The reason for this is documented in theC++ standard: at the end of acatch-handler belonging to a constructor or destructor function try block, theoriginal exception is automatically rethrown.
The exception is not rethrown if the handler itself throws another exception,offering the constructor or destructor a way to replace a thrown exception by another one. Theexception is only rethrown if it reaches the end of the catch handler of aconstructor or destructor function try block. Exceptions caught by nestedcatch handlers are not automatically rethrown.
As only constructors and destructors rethrow exceptions caught by theirfunction try block catch handlers the run-time error encountered in the aboveexample may simply be repaired by providingmain with its own function tryblock:
int main() try { Composer c; } catch (...) {}Now the program runs as planned, producing the following output:
Throw's exception handled locally by Throw() Composer() caught exception as well
A final note: if a function defining a function try block also declares anexception throw list then only the types of rethrown exceptions must matchthe types mentioned in the throw list.
The following example illustrates this situation in its prototypicalform. The constructor of the classIncomplete first displays a messageand then throws an exception. Its destructor also displays a message:
class Incomplete { public: Incomplete() { cerr << "Allocated some memory\n"; throw 0; } ~Incomplete() { cerr << "Destroying the allocated memory\n"; } }; Next,main() creates anIncomplete object inside atryblock. Any exception that may be generated is subsequently caught: int main() { try { cerr << "Creating `Incomplete' object\n"; Incomplete{}; cerr << "Object constructed\n"; } catch(...) { cerr << "Caught exception\n"; } } When this program is run, it produces the following output:Creating `Incomplete' object Allocated some memory Caught exception
Thus, ifIncomplete's constructor would actually have allocated somememory, the program would suffer from a memory leak. To prevent this fromhappening, the following counter measures are available:
try block, allowing the exception to be caughtby the constructor itself. This approach is defensible when the constructoris able to repair the cause of the exception and to complete its constructionas a valid object.try block within the constructor's body won't be able to catch the thrown exception. Thisalways results inthe exception leaving the constructor and the object is not considered to havebeen properly constructed. Atry block may include the memberinitializers, and thetry block's compound statement becomes theconstructor's body as in the following example:class Incomplete2{ Composed d_composed; public: Incomplete2() try : d_composed(/* arguments */) { // body } catch (...) {}};An exception thrown by either the member initializers or the bodyresults in the execution never reaching the body's closing curly brace. Insteadthe catch clause is reached. Since the constructor's body isn't properlycompleted the object is not considered properly constructed and eventually theobject's destructor won't be called.
The catch clause of a constructor's functiontry block behavesslightly different than a catch clause of an ordinary functiontryblock. An exception reaching a constructor's functiontry block may betransformed into another exception (which is thrown from the catch clause) butif no exception is explicitly thrown from the catch clause the exceptionoriginally reaching the catch clause is always rethrown. Consequently, there'sno way to confine an exception thrown from a base class constructor or from amember initializer to the constructor: such an exceptionalways propagatesto a more shallow block and in that case the object's construction is alwaysconsidered incomplete.
Therefore, if incompletely constructed objects throw exceptions thenthe constructorremains responsible for preventing memory(generally: resource) leaks. There are several ways to realize this:
shared_ptrobjects are, after all, objects.class Incomplete2{ Composed d_composed; char *d_cp; // plain pointers int *d_ip; public: Incomplete2(size_t nChars, size_t nInts) try : d_composed(/* arguments */), // might throw d_cp(0), d_ip(0) { try { preamble(); // might throw d_cp = new char[nChars]; // might throw d_ip = new int[nChars]; // might throw postamble(); // might throw } catch (...) { delete[] d_cp; // clean up delete[] d_ip; throw; // retrow the exception } } catch (...) { // maybe write a log-entry, but also throws // the original exception }};On the other hand,C++ supports constructor delegation, so an objectmay have been completely constructed according to theC++ run-time system,but yet its (delegating) constructor may throw an exception, as illustrated bythe next example:
1: #include <iostream> 2: using namespace std; 3: 4: class Delegate 5: { 6: char *d_p1; 7: char *d_p2; 8: 9: public: 10: Delegate() // succeeds -> object constructed 11: : 12: Delegate(0) 13: { 14: d_p2 = new char[10]; 15: cout << "default, throws...\n"; 16: throw 12; // but considered constructed 17: } 18: ~Delegate() 19: { 20: delete[] d_p1; 21: delete[] d_p2; 22: cout << "destructor\n"; 23: } 24: 25: private: 26: Delegate(int x) // completes OK 27: : 28: d_p1(0), 29: d_p2(0) 30: { 31: cout << "delegated\n"; 32: } 33: }; 34: 35: int main() 36: try 37: { 38: Delegate del; // throws 39: 40: cout << "never reached\n"; 41: } // del's destructor is called here 42: catch (...) 43: { 44: cout << "main's catch clause\n"; 45: }Here it is the responsibility ofDelegate's designer to ensure thatthe throwing default constructor does not invalidate the actions performed bytheDelegate(int x) constructor. The latter constructor is called (line12) by the default constructor, and merely initializes (lines 28, 29) the datamembers at lines 6 and 7. Next, the default constructor, after allocating somememory, throws an exception (line 16). In fact, an exception may be called atany point, since the destructor (line 18) will be called automatically anyway(line 41). If multiple exceptions could be thrown thenDelegate can definean enumeration and a data member of that enumeration type, which is set to theenum value indication the nature of the next exception (if it is thrown), sothe destructor can handle the exception according to its type.
tryblock is therefore a violation of the standard: exceptions caught by afunctiontry block's catch clause have already left the destructor's body.If --in violation of the standard-- the destructoris provided with afunctiontry block and an exception is caught by thetry block thenthat exception is rethrown, similar to what happens in catch clauses ofconstructor functions'try blocks.The consequences of an exception leaving the destructor's body is notdefined, and may result in unexpected behavior. Consider the following example:
Assume a carpenter builds a cupboard having a single drawer. The cupboardis finished, and a customer, buying the cupboard, finds that the cupboard canbe used as expected. Satisfied with the cupboard, the customer asks thecarpenter to build another cupboard, this time havingtwodrawers. When the second cupboard is finished, the customer takes it home andis utterly amazed when the second cupboard completely collapses immediatelyafter it is used for the first time.
Weird story? Then consider the following program:
int main() { try { cerr << "Creating Cupboard1\n"; Cupboard1{}; cerr << "Beyond Cupboard1 object\n"; } catch (...) { cerr << "Cupboard1 behaves as expected\n"; } try { cerr << "Creating Cupboard2\n"; Cupboard2{}; cerr << "Beyond Cupboard2 object\n"; } catch (...) { cerr << "Cupboard2 behaves as expected\n"; } } When this program is run it produces the following output:Creating Cupboard1 Drawer 1 used Cupboard1 behaves as expected Creating Cupboard2 Drawer 2 used Drawer 1 used terminate called after throwing an instance of 'int' Abort
The finalAbort indicates that the program has aborted instead ofdisplaying a message likeCupboard2 behaves as expected.
Let's have a look at the three classes involved. The classDrawer has noparticular characteristics, except that its destructor throws an exception:
class Drawer { size_t d_nr; public: Drawer(size_t nr) : d_nr(nr) {} ~Drawer() { cerr << "Drawer " << d_nr << " used\n"; throw 0; } }; The classCupboard1 has no special characteristics at all. It merelyhas a single composedDrawer object: class Cupboard1 { Drawer left; public: Cupboard1() : left(1) {} }; The classCupboard2 is constructed comparably, but it has twocomposedDrawer objects: class Cupboard2 { Drawer left; Drawer right; public: Cupboard2() : left(1), right(2) {} };WhenCupboard1's destructor is calledDrawer's destructor iseventually called to destroy its composed object. This destructor throws anexception, which is caught beyond the program's firsttry block. Thisbehavior is completely as expected.
A subtlety here is thatCupboard1's destructor (and henceDrawer'sdestructor) is activatedimmediately subsequent to its construction. Itsdestructor is called immediately subsequent to its construction asCupboard1() defines an anonymous object. As a result theBeyondCupboard1 object text is never inserted intostd::cerr.
Because ofDrawer's destructor throwing an exception a problem occurswhenCupboard2's destructor is called. Of its two composed objects, thesecondDrawer's destructor is called first. This destructor throws anexception, which ought to be caught beyond the program's secondtryblock. However, although the flow of control by then has left the context ofCupboard2's destructor, that object hasn't completely been destroyed yetas the destructor of its other (left)Drawer still has to be called.
Normally that would not be a big problem: once an exception is thrown fromCupboard2's destructor any remaining actions would simply be ignored,albeit that (as both drawers are properly constructed objects)left'sdestructor would still have to be called.
This happens here too andleft's destructoralso needs to throw anexception. But as we've already left the context of the secondtry block,the current flow control is now thoroughly mixed up, and the program has noother option but to abort. It does so by callingterminate(), which inturn callsabort(). Here we have our collapsing cupboard having twodrawers, even though the cupboard having one drawer behaves perfectly.
The program aborts since there are multiple composed objects whosedestructors throw exceptions leaving the destructors. In this situation one ofthe composed objects would throw an exception by the time the program's flowcontrol has already left its proper context causing the program to abort.
TheC++ standard therefore understandably stipulates that exceptionsmaynever leave destructors. Here is the skeleton of a destructor whose code might throwexceptions. No functiontry block but all the destructor's actions areencapsulated in atry block nested under the destructor's body.
Class::~Class() { try { maybe_throw_exceptions(); } catch (...) {} }