[Exceptional C++ Style] Item 13: A Pragmatic Look at Exception Specifications
Paul Grenyer
paul at paulgrenyer.co.uk
Tue Dec 7 03:18:34 EST 2004
--- Forwarded on behalf of David Sullivan ----
This chapter discusses the use of exception specifications. It discusses
when and how they are used. Since the last C++ standard, what have we learnt
about the use of this feature following practical experience of it.
Exception Specification Usage
// Can throw anything.
int Func();
// Cannot throw anything.
int Gunc() throw();
// Can throw either type A or B.
int Hunc() throw(A, B);
An exception spscification tells the compiler to do a run-time check that
guarantees that only exceptions of certain types will be throw. If the
guarantee is violated, the function unexpected is called. The specification
below gurantees that function f will emit only exceptions of type A or B.
int f() throw(A, B);
In this case, if an exception of type other than A or B is thrown, the
function unexpected is called.
int f() throw(A, B) {
throw; // unexpected is called
}
The default unexpected function can overidden using the standard
set_unexpected function.
void MyUnexpectedHandler { .... }
std:set_unexpected( &MyUnexpectedHandler );
The user defined unexpected function cannot return via a usual function
return. It can
- either translate the exception to a form permissable by the exception
specification, by throwing an exception from the unexpected handler that
complies with the specification of the original thrown function.
- or call terminate which ends the program. (The terminate function can be
replaced, but any replacement must end the program).
A Shadow Type System
Is an exception specification part of the function's type? The exception
specification has been described as a "shadow type system". There are
anomolies between C++'s type system and the use of exception specifications.
void f() throw(A, B);
typedef void(*PF)() throw (A, B); // syntax error
PF *pf = f;
But similar code works without the typedef-:
void f() throw(A, B);
void(*pf)() throw (A, B); // valid
PF *pf = f; // valid
C++ does not allow an exception specifcation on a typedef'd function.
The use of assignment to a pointer to a function is restricted to a
specification that must include at least the original throw types.
void f() throw(A, B);
void(*pf)() throw (A, B, C);
pf = f; // valid, pf's throw type list is less restrictive
Exception specifications also affect the typing of a virtual function when
the function is overridden.
class C {
virtual void f() throw(A, B);
}
class D: C {
void f(); // error
}
Summarising, the type system is affected by the use of exception
specifications and different rules may apply.
Misunderstandings
There is confusion about what exception specifications provide. Several C++
experts claim that C++ programmers believe that an exception specification
guarantee enforces that only the types specified are thrown. This is not so.
A broken promise results a throw to the unexpected function call, and that
usually results in the program ending via the terminate function.
Once inside the unexpected function, there are two choices-:
- Throw an exception which is allowed by the specification. The unexpected
function will normally br user-defined as the global unexpected function is
very unlikely to accomadate this strategy. If an permissable exception is
not thrown, the terminate function is run.
- Throw an exception which is still disallowed by the specification (such as
bad_exception). If the specification allows a bad_exception, that is the
exception that is propagated. If it does not, the terminate function again
looms.
Summary of what Exception Specifications do
- Enforce (not guarantee) at run-time that functions will throw only listed
exceptions (possibly none)
- Enable or prevent compiler optimisations based on having to check whether
listed exceptions are indeed being thrown.
int Hunc() throw(A, B) {
return Junc();
}
is translated by compiler to
int Hunc() {
return Junc();
}
catch (A) {
throw;
}
catch (B) {
throw;
}
catch (...) {
std::unexpected();
}
The compiler has to do more work to enforce at run-time that only an
exception that has been specified is thrown. It cannot assume that only
certain exceptions may thrown.
Impact of Exception Specifications
This chapter has shown use of exception specifications imposes a performance
penalty.
Other factors which may affect run-time performance when using exception
specifications are-:
- Some compilers will not inline a function with a performance
specification.
- Some compilers will add the compiler generated try/catch blocks, even when
the function cannot throw an exception. The use of exception specifications
increases coupling. e.g. Removing a type from a base class virtual
function's exception specification will break derived classes at a stroke.
Summary
- Use of exception specifications imposes a performance panalty.
- A run-time unexpected error may not be desirable if an unspecified
exception is thrown.
- Exception specifications cannot be used with function templates anyway as
it's difficult to determine what the parameterised types will throw.
- Do not use exception specifications.
More information about the Effective-cpp
mailing list