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
unexpected
is 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 7
extern 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
unexpected
is 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,unexpected
will 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 15
class 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_alloc
thrown byoperator new
andoperator 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 8
class 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_exception
by rethrowing the current exception in the customizedunexpected
function, and includebad_exception
or its base classexception
in the exception specifications.1 2 3 4 5 6
void 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