Item 14: Use exception specifications judiciously.
There's no denying it: exception specifications have appeal. They make code easier to understand, because they explicitly state what exceptions a function may throw. But they're more than just fancy comments. Compilers are sometimes able to detect inconsistent exception specifications during compilation. Furthermore, if a function throws an exception not listed in its exception specification, that fault is detected at runtime, and the special function unexpected
is automatically invoked. Both as a documentation aid and as an enforcement mechanism for constraints on exception usage, then, exception specifications seem
As is often the case, however, beauty is only skin deep. The default behavior for unexpected
is to call terminate
, and the default behavior for terminate
is to call abort
, so the default behavior for a program with a violated exception specification is to halt. Local variables in active stack frames are not destroyed, because abort
shuts down program execution without performing such cleanup. A violated exception specification is therefore a cataclysmic thing, something that should almost never
Unfortunately, it's easy to write functions that make this terrible thing occur. Compilers only partially check exception usage for consistency with exception specifications. What they do not check for what the
Consider a declaration for a function f1
that has no exception specification. Such a function may throw any kind of
extern void f1(); // might throw anything
Now consider a function f2
that claims, through its exception specification, it will throw only exceptions of type int
:
void f2() throw(int);
It is perfectly legal C++ for f2
to call f1
, even though f1
might throw an exception that would violate f2
's exception
void f2() throw(int) { ... f1(); // legal even though f1 might throw // something besides an int ... }
This kind of flexibility is essential if new code with exception specifications is to be integrated with older code lacking such
Because your compilers are content to let you call functions whose exception specifications are inconsistent with those of the routine containing the calls, and because such calls might result in your program's execution being terminated, it's important to write your software in such a way that these kinds of inconsistencies are minimized. A good way to start is to avoid putting exception specifications on templates that take type arguments. Consider this template, which certainly looks as if it couldn't throw any
// a poorly designed template wrt exception specifications template<class T> bool operator==(const T& lhs, const T& rhs) throw() { return &lhs == &rhs; }
This template defines an operator==
function for all types. For any pair of objects of the same type, it returns true
if the objects have the same address, otherwise it returns false
.
This template contains an exception specification stating that the functions generated from the template will throw no exceptions. But that's not necessarily true, because it's possible that operator&
(the address-of operator see Item E45) has been overloaded for some types. If it has, operator&
may throw an exception when called from inside operator==
. If it does, our exception specification is violated, and off to unexpected
we
This is a specific example of a more general problem, namely, that there is no way to know anything about the exceptions thrown by a template's type parameters. We can almost never provide a meaningful exception specification for a template, because templates almost invariably use their type parameter in some way. The conclusion? Templates and exception specifications don't
A second technique you can use to avoid calls to unexpected
is to omit exception specifications on functions making calls to functions that themselves lack exception specifications. This is simple common sense, but there is one case that is easy to forget. That's when allowing users to register callback
// Function pointer type for a window system callback // when a window system event occurs typedef void (*CallBackPtr)(int eventXLocation, int eventYLocation, void *dataToPassBack);
// Window system class for holding onto callback // functions registered by window system clients class CallBack { public: CallBack(CallBackPtr fPtr, void *dataToPassBack) : func(fPtr), data(dataToPassBack) {}
void makeCallBack(int eventXLocation, int eventYLocation) const throw();
private: CallBackPtr func; // function to call when // callback is made
void *data; // data to pass to callback }; // function
// To implement the callback, we call the registered func- // tion with event's coordinates and the registered data void CallBack::makeCallBack(int eventXLocation, int eventYLocation) const throw() { func(eventXLocation, eventYLocation, data); }
Here the call to func
in makeCallBack
runs the risk of a violated exception specification, because there is no way of knowing what exceptions func
might
This problem can be eliminated by tightening the exception specification in the CallBackPtr
typedef:5
typedef void (*CallBackPtr)(int eventXLocation, int eventYLocation, void *dataToPassBack) throw();
Given this typedef, it is now an error to register a callback
function that fails to guarantee it throws
// a callback function without an exception specification void callBackFcn1(int eventXLocation, int eventYLocation, void *dataToPassBack);
void *callBackData;
...
CallBack c1(callBackFcn1, callBackData); // error! callBackFcn1 // might throw an exception
// a callback function with an exception specification void callBackFcn2(int eventXLocation, int eventYLocation, void *dataToPassBack) throw();
CallBack c2(callBackFcn2, callBackData); // okay, callBackFcn2 has a // conforming ex. spec.
This checking of exception specifications when passing function pointers is a relatively recent addition to the language, so don't be surprised if your compilers don't yet support it. If they don't, it's up to you to ensure you don't make this kind of
A third technique you can use to avoid calls to unexpected
is to handle exceptions "the system" may throw. Of these exceptions, the most common is bad_alloc
, which is thrown by operator
new
and operator
when a memory allocation fails (see Item 8). If you use the new
operator (again, see Item 8) in any function, you must be prepared for the possibility that the function will encounter a bad_alloc
Now, an ounce of prevention may be better than a pound of cure, but sometimes prevention is hard and cure is easy. That is, sometimes it's easier to cope with unexpected exceptions directly than to prevent them from arising in the first place. If, for example, you're writing software that uses exception specifications rigorously, but you're forced to call functions in libraries that don't use exception specifications, it's impractical to prevent unexpected exceptions from arising, because that would require changing the code in the
If preventing unexpected exceptions isn't practical, you can exploit the fact that C++ allows you to replace unexpected exceptions with exceptions of a different type. For example, suppose you'd like all unexpected exceptions to be replaced by UnexpectedException
objects. You can set it up like
class UnexpectedException {}; // all unexpected exception // objects will be replaced // by objects of this type void convertUnexpected() // function to call if { // an unexpected exception throw UnexpectedException(); // is thrown }
and make it happen by replacing the default
unexpected
function with convertUnexpected
:
set_unexpected(convertUnexpected);
Once you've done this, any unexpected exception results in convertUnexpected
being called. The unexpected exception is then replaced by a new exception of type UnexpectedException
. Provided the exception specification that was violated includes UnexpectedException
, exception propagation will then continue as if the exception specification had always been satisfied. (If the exception specification does not include UnexpectedException
, terminate
will be called, just as if you had never replaced unexpected
.)
Another way to translate unexpected exceptions into a well known type is to rely on the fact that if the unexpected
function's replacement rethrows the current exception, that exception will be replaced by a new exception of the standard type bad_exception
. Here's how you'd arrange for that to
void convertUnexpected() // function to call if { // an unexpected exception throw; // is thrown; just rethrow } // the current exception set_unexpected(convertUnexpected); // install convertUnexpected // as the unexpected // replacement
If you do this and you include bad_exception
(or its base class, the standard class exception
) in all your exception specifications, you'll never have to worry about your program halting if an unexpected exception is encountered. Instead, any wayward exception will be replaced by a bad_exception
, and that exception will be propagated in the stead of the original
By now you understand that exception specifications can be a lot of trouble. Compilers perform only partial checks for their consistent usage, they're problematic in templates, they're easy to violate inadvertently, and, by default, they lead to abrupt program termination when they're violated. Exception specifications have another drawback, too, and that's that they result in unexpected
being invoked even when a higher-level caller is prepared to cope with the exception that's arisen. For example, consider this code, which is taken almost verbatim from Item 11:
class Session { // for modeling online public: // sessions ~Session(); ... private: static void logDestruction(Session *objAddr) throw(); }; Session::~Session() { try { logDestruction(this); } catch (...) { } }
The Session
destructor calls logDestruction
to record the fact that a Session
object is being destroyed, but it explicitly catches any exceptions that might be thrown by logDestruction
. However, logDestruction
comes with an exception specification asserting that it throws no exceptions. Now, suppose some function called by logDestruction
throws an exception that logDestruction
fails to catch. This isn't supposed to happen, but as we've seen, it isn't difficult to write code that leads to the violation of exception specifications. When this unanticipated exception propagates through logDestruction
, unexpected
will be called, and, by default, that will result in termination of the program. This is correct behavior, to be sure, but is it the behavior the author of Session
's destructor wanted? That author took pains to handle all possible exceptions, so it seems almost unfair to halt the program without giving Session
's destructor's catch
block a chance to work. If logDestruction
had no exception specification, this I'm-willing-to-catch-it-if-you'll-just-give-me-a-chance scenario would never arise. (One way to prevent it is to replace unexpected
as described
It's important to keep a balanced view of exception specifications. They provide excellent documentation on the kinds of exceptions a function is expected to throw, and for situations in which violating an exception specification is so dire as to justify immediate program termination, they offer that behavior by default. At the same time, they are only partly checked by compilers and they are easy to violate inadvertently. Furthermore, they can prevent high-level exception handlers from dealing with unexpected exceptions, even when they know how to. That being the case, exception specifications are a tool to be applied judiciously. Before adding them to your functions, consider whether the behavior they impart to your software is really the behavior you
typedef
." I don't know why. If you need a portable solution, you must it hurts me to write this make CallBackPtr
a macro, sigh.