TITLE: exceptions and throw() (Newsgroups: comp.std.c++, 22 Jul 98) STERN: Josh Stern >> >I'd like to ask about how much (if any) efficiency is gained >> >with existing C++ compilers when throw() is added to >> >all function definitions where it is permissible to add it. MYERS: ncm@nospam.cantrip.org (Nathan Myers) >> If the function does anything that *might* throw (including call >> a function not declared throw() itself) the compiler is obliged >> to add a try/catch block to call unexpected() if something throws. ALLANW: AllanW@my-dejanews.com >In this case, is the compiler still required to "clean up" objects >that were constructed and not destructed? If not, the post-call >code becomes considerably simpler. > > function a() { // Might throw > if (0==rand()) throw 1; > } > > function b() throw() { > Object myObject(1); > for (int i=0; i<5; ++i) a(); > } ABRAHAMS: abrahams@motu.com I think these functions are missing return values of type "function" ;) ALLANW: >The programmer has (incorrectly, in this case) guaranteed that a() >will not throw. If a does throw, is myObject guaranteed to call >the destructor? Because if not, we could simplify the call to a() >by simply jumping to unexpected() if a() throws, rather than >unwinding the stack first. ABRAHAMS: As Bjarne Stroustrup points out in his "C++ Programming Language, 3rd Edition", b's definition is precisely equivalent to: void b() { try { Object myObject(1); for (int i=0; i<5; ++i) a(); } catch(...) { std::unexpected(); } } So if a() throws, myObject will be destroyed before the catch block is entered, i.e. before unexpected() is called. If myObject were not destroyed, the rewrite of b above would have different semantics from the original version. ALLANW: >If stack unwinding is still guaranteed even in throw() functions, >then it seems impossible to implement exceptions without generating >code much fatter than C ever generated. ABRAHAMS: I wouldn't be so quick to jump to that conclusion. The case you are talking about handling is one in which an unexpected error occurs and you want to terminate the program. Does that arise often in your C code? If so, start calling terminate() instead of throwing exceptions in the C++ version ;). As I pointed out in a previous post, getting an optimization benefit from exception specifications requires two things: 1. that you are disciplined about using throw() all the way down to the leaf functions (or at least through many levels). 2. That your compiler supports the appropriate optimizations. There are two ways to manage exceptions in any function: with try/catch blocks or with cleanup objects. Using try/catch can more easily have a lower runtime cost (in the case where no exceptions occur), since you are giving the compiler very complete information about which code is dedicated to error handling. This code can be moved to separate areas of memory to improve locality of reference and remove local branching. Using cleanup objects can more easily have a lower cost in space, since the code to execute the destructors on exit is already generated as part of the function body. A good compiler can arrange its exception tables to jump into the part of the function where the destructors are being executed. The more potential places an error can occur such that the program needs to begin unwinding with the same destructor, the greater its potential code size advantage over the corresponding "C" code. I'm thinking of something like this: if ( err = op1() ) goto cleanup; if ( err = op2() ) goto cleanup; if ( err = op3() ) goto cleanup; if ( err = op4() ) goto cleanup; ... the C++ version can turn all of those individual test/branch cases into a single exception-table entry which specifies the range of addresses from which the throw() occurs and the address of the cleanup code. Most of the time, I try to avoid try/catch and instead use objects to manage resources and program state. This results in cleaner and usually smaller code which is easier to understand (there are fewer special cases to examine). In time-critical code like the STL, however, I use try/catch for the best possible performance. My first concern is readability/maintainability, though, and these performance guidelines may not hold across all implementations. Of course, your mileage may vary.