Exception specifications provide a documentation aid and an enforcement mechanism for constraints on exception usage, but they are only partly checked by compilers and they are easy to violate inadvertently.
The good points
-
Explicitly state what exception a function may throw
-
Compilers are sometimes able to detect inconsistent exception specfications during compilation
-
If the inconsistency is not found during compilation but detected at runtime, the special funciton
unexpectedis automatically invoked to constrain exception usage.- There is a reason for compilers to partially check exception usage for consistency with exception: the language standard prohibits compilers from rejecting a call to a function that might violate the exception specification of the function making the call in order to integrate with older code lacking such specifications:
1 2 3 4 5 6 7extern void f1(); // might throw anything void f2() throw (int) { ... f1(); // legal even if f1 might throw sth other than an int ... }
The unwanted points
-
The default behavior for
unexpectedis to callterminate, which by default will callabort, preventing possible high-level exception handlers from dealing with unexpected exceptions.Sometimes the default behavior of immediate program termination is not what we want. For example, like the example code below, when an unanticipated exception propagates from inside the
logDestruction(which isn’t supposed to happen due to the assertionthrow()in exception specification afterlogDestruction, but it’s possible due to a call to some other function that throws), by default,unexpectedwill be called, and that will result in termination of the program, without letting the high-level destructor to catch and deal with the exception.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15class Session { public: ~Session(); ... private: static void logDestruction(Session *objAddr) throw(); }; Session::~Session() { try { logDestruction(this); } catch (...) {} }
The solution
To avoid calls to unexpected:
- A good way to start is to avoid putting exception specifications on templates that take type argements, because there’s no way to know anything about the exceptions thrown by a template’s type parameters.
- A second technique is to omit exception specifications on functions making calls to functions that themselves lack exception specifications.
- A third technique is to handle exceptions “the system” may throw, such as
bad_allocthrown byoperator newandoperator new[]when a memory allocation fails (MECpp item 8).
To cope with unexpected exceptions:
-
Exploit the fact that C++ allows us to replace unexpected exceptions with exceptions of a different type (
UnexpectedException), and add this type in the exception specification.1 2 3 4 5 6 7 8class UnexpectexException {}; // all unexpected exception obj. will be replaces // by obj. of this type void convertUnexpected() // function to call is an unexpected exception is thrown { throw UnexpectedException(); } set_unexpected(convertUnexpected); // replace the default `unexpected` function with `convertUnexpected` -
Another way is to translate unexpected exceptions into
bad_exceptionby rethrowing the current exception in the customizedunexpectedfunction, and includebad_exceptionor its base classexceptionin the exception specifications.1 2 3 4 5 6void convertUnexpected() // function to call is an unexpected exception is thrown { throw; // just rethrow the current exception } set_unexpected(convertUnexpected); // install convertUnexpected as the unexpected replacement