This column continues my
development of programming guidelines for C++ exceptions. I
discussed guidelines for coping with exceptions (catching and
propagating) in[1], and the previous issue of this column was
devoted to guidelines for throwing exceptions[2]. Most of the
guidelines in this installment have something to do with
exception specifications. Of the C++ exception triumvirate

throwing

handling (catching and
propagating)

specifying

exception specifications seem to
cause the most confusion (which is not the same as being the most
difficult). Some users wonder why they exist at all since they
are not checked at compile time; other users apparently think
they are a panacea for all their other exception handling
problems. Like exceptions themselves, exception specifications
are a tool. Like most tools, it takes some practice to understand
how and when they should be used.

Let's first review how exception
specifications work. An exception specification is part of a
function signature. It consists of the keyword throw written
after the parameter list and followed by a parenthesized list of
types. An exception specification says that the function can only
throw objects of the types listed in the specification. For
example:

X get(int i)
throw(out_of_range);

indicates that function get()
can throw exceptions of type out_of_range (one of the
predefined exception classes), or any class publicly derived from
out_of_range. Likewise:

void foo() throw();

says that function foo()
can not throw any exceptions. Exception specifications are
completely optional, and the absence of an exception
specification indicates that there are no restrictions on the
possible exceptions. In particular, the absence of an exception
specification is NOT an indication that a function does not throw
any exceptions (the throw() form is used for that
indication).

By now, if you know anything at
all about exceptions, you have probably heard that exception
specifications are not checked at compile time. Instead, they are
enforced at run time. This may seem somewhat strange, given C++'s
almost fanatical avoidance of runtime checks. It also may seem
strange given C++s strong type checking, which is done at
compile time (with some help from the linker). Its just not
like C++ to have a construct (one involving types, at that) that
is unchecked at compile time. You may even think that exception
specifications could/should be checked at compile time and wonder
why they are not.

A little thought will show that
attempting to check exception specifications at compile time
rapidly leads to violations of far more fundamental C++
philosophies. Consider the following example (only slightly
contrived).

X get(int i)
throw(out_of_range);

int find(X& x) throw()

{

for (int i = 0; i < size();
++i) {

if (x == get(i)) return i;

}

return -1;

}

Here, function find()
calls a function, get(), which states that it can throw an
exception. Function find() however, has an exception
specification that states that it will not throw any exceptions.
If exception specifications were checked a compile time, then
function find() would generate an error message on the
line that calls get(). Nevertheless, this programmer has
carefully written find() to include a test (i <
size()) that will avoid the condition which would cause get()
to throw an exception. In C++ (as in C) the assumption is that
the programmer knows what he is doing. C++ programmers expect
this to compile without even a warning. Certainly, I would not
want to write code like this

int find(X& x) throw()

try {

for (int i = 0; i < size();
++i) {

if (x == get(i)) return i;

}

return -1;

}

catch (out_of_range) {

return -1;

}

just to get my functions to
compile cleanly. By the way, thats a function-try-block you
see there -- the body of the function is the try block.
Yes, it is legal in (Draft) Standard C++, and no, it will not
work on your compiler, at least not yet.

Obviously, this line of reasoning
leads to complete absurdities when applied to legacy code that
have no exception specifications at all. A compile time check
would either:

force any function that
called a legacy function to also have no exception
specification (essentially destroying their usefullness).

force the programmer to code
totally unnecessary try/catch blocks like the above (with
catch(...) clauses) to convince the compiler to
accept the exception specification.

force the compiler to be
able to check the code of the called function, (and any
function it calls, and so on), to make sure it did not
throw any exceptions.

None of these situations was
deemed acceptable for C++, so there is no compile time checking
of exception specifications.

The argument that a compiler
should at least generate a warning if a function contains a throw
statement that violates the function's exception specification
makes more sense, but there are problems with even that idea.
Maybe the function unexpected_handler() has been replaced
with one that will rethrow a valid exception (more on this
below). Maybe the programmer knows that the function will never
be called, or that the path containing the throw statement
will never be executed. Maybe both of these scenarios are silly,
but neither one of them is the compiler's business. A compiler
might reasonably generate a warning about a throw statement that
it recognized would always be executed, in the same way that many
of them generate warnings about code that can never be reached,
but otherwise, flow of control in a function is up to the
programmer.

Problems With Exception
Specifications

The problem is that exception
specifications, unlike anything else in C++, are not a statement
of what should happen, but a statement of what might
happen and a guarantee of what will not happen. When you see an
exception specification, it tells you two things:

under certain circumstances
the function might fail and propagate an exception of one
of the types indicated back to the caller.

these are the only error
conditions that the caller will have to worry about.

Note carefully that the latter
statement does not indicate that these are the only error
conditions that can occur in the function, only that all other
conditions will be handled within the function. As a client of a
function, you may not like what happens when the function handles
certain errors, but at least you do not have to worry about
handling anything not stated in the exception specification.

Rather than say exception
specifications are checked at runtime, I like to use the
term "enforced". There are two separate points I want
to make about this runtime enforcement. The first is that the
exception specification on the definition of a function
such as

void foo() throw(X, Y)

{ /* body */ }

results in the compiler
generating code equivalent to the following:

void foo()

try {

/* body */

}

catch (X&) {

throw; // rethrow exception of
type X

}

catch (Y&) {

throw; // rethrow exception of
type Y

}

catch (...) {

unexpected(); // disallow all
other exception types

}

As you can see, any attempt to
propagate an exception out of foo() that does not match
its exception specification results in unexpected() being
called.

At the risk of belaboring a point
far too much, I want to emphasize again that an exception
specification on a function guarantees that only those exception
types specified can propagate out of the function. It does not
guarantee anything else. I belabor this point because I have
repeatedly come across code where the writer seemed to think that
putting an exception specification of throw() on a
function was some sort of panacea that would guarantee not having
to worry about exceptions. It was almost as if they expected throw()
to somehow disable the compiler's ability to generate exceptions.
A throw() specification may keep the compiler from
propagating an exception, but it sure as heck does not mean that
you can ignore the exception when it occurs. You do not get a
chance to handle it either, and that is a problem.

For example, the following will
compile and link.

void foo() throw ()

{

throw exception();

{

At run-time however, calling foo()
is just a round-about way of calling abort():

foo throws exception

exception
specification violation calls unexpected()

unexpected()
call unexpected_handler()

default unexpected_handler()
calls terminate()

terminate()
calls terminate_handler()

default terminate_handler()
calls abort()

Supplying different versions of
either unexpected_handler() or terminate_handler()
will just make this path longer without changing its final
destination (terminate_handler() does not have to call abort(),
but it does have to terminate the program; more on this below).

The second point I wanted to make
about the runtime enforcement of exception specifications is that
exception specifications on the declaration of a function
are basically nothing but comments. The compiler will insist that
the type names used in the exception specification are in scope,
but otherwise there is no code generated at a call site of a
function that has anything to do with the exception
specification. Even though the compiler will make sure the
exception specification in the header (i.e. the comment) matches
that on the function itself , there is nothing that will warn
clients of the function if the exception specification changes.

As noted above, if an exception
specification is violated at runtime, the standard library
function unexpected() is called. Function unexpected()
in turn calls the current unexpected_handler() function.
The user can supply a replacement for the default unexpected_handler()
function by calling the function set_unexpected(). A user
defined unexpected_handler() is fairly limited in what it
can do, however. It can not return. It must either (i) throw an
exception, preferably one allowed by the exception specification,
or (ii) terminate the program. If a user supplied unexpected_handler()
throws an exception, and that exception is not one allowed by
the exception specification, then one of two things happens: (1)
if the exception specification contains the predefined exception bad_exception,
then the thrown exception is replaced by a bad_exception exception;
(2) if the exception specification does not contain the
predefined exception bad_exception, then terminate()
is called.

Function terminate() is
the exception mechanism's escape hatch. It is called whenever the
exception runtime encounters a situation that it can not handle.
It is also the default behavior for handling unexpected
exceptions. By default terminate() calls abort().
It can call a user supplied terminate_handler() function,
but such a function must still terminate the program.

With that preface, let's now
examine some specific guidelines for how to use exception
specifications.

Exception Specification
Guidelines

Guideline 1. - Code
exception specifications as a statement of what the function
does, not what you think it should do.

Given the way exception
specifications actually are enforced at runtime, this should be
obvious, but bears repeating. We can not just write throw()
on a function declaration and then blithely assume we have
guaranteed that the program will run along happily without
encountering any exceptions from this function. All we have
guaranteed is that the program will abort if we are wrong (Cay
Horstmann calls this the death penalty -- he may have
named it more appropriately than he knows).

This can be a significant problem
during development and a major headache during maintenance.
Suppose I have carefully checked and determined that my function parse_it()
does not throw any exceptions, or call any functions that throw
exceptions. So I put an exception specification of throw()
on it (and all my users love me for it). Down the road, a slight
change is necessary, and I (or someone else) decides to switch
from using character arrays and the C library string functions to
using the standard C++ library string class. Now, my function
might someday encounter a bad_alloc exception. Sure, it is
not likely, but it is possible. If a parameter type changes, or
the number of parameters in a function signature changes, the
compiler will complain when the library is rebuilt. Nothing at
all warns that parse_it()'s exception specification is now
a program abort waiting to happen.

In a very important and very real
sense, a program abort may be exactly what we want to happen in
such a case. As discussed at some length in [3] and [4], as well
as my own [1], when exceptions go propagating up the call stack
and through functions of a program that were not designed to
correctly propagate exceptions, they can wreak all kinds of
havoc. Exception specifications are one of the tools C++ gives us
for avoiding the havoc of exception propagation. Function unexpected()
is named appropriately. It is the last line of defense against
the destruction that can be caused by unexpected exceptions
propagating through our code. Its selection of remedies is pretty
crude, and its final solution is brutal, but it will keep our
programs from executing into limbo-land because some propagating
exception left some object in an invalid state.

In the case of parse_it(),
some of my users may have written their code in an unsafe manner
because they knew that parse_it() threw no exceptions. A
program abort on a bad_alloc exception may be doing them a
favor. On the other hand, parse_it() may have another
client who has written their code in an exception safe manner,
and their application may have had a bad_alloc handler at
the appropriate place. Now, my invalid exception specification
simply guarantees that their well designed program does not work
as intended. This, in turn, is guaranteed to not make them happy
(I speak from experience).

This leads to guidelines 2, 3 and
4.

Guideline 2. - Avoid
exception specifications if possible. Limit them to important
public interface functions.

In general, if we do not really
need an exception specification, then we are probably better off
without one. It simplifies maintenance, and it forces users of
our functions (which are probably ourselves) to write code that
can correctly propagate exceptions. Only those functions that
make up a major public interface to a reusable class or library
are good candidates for really needing an exception
specification. Even then, we have to put off writing exception
specifications until after we have done the implementation, which
may mean that early users of our class or library have to get by
without them.

Remember: no specification does
not mean that the function does not throw any exceptions, nor
does it mean that the function will throw some unknown exception.
It simply means that the exception specification is unknown. In
other words, the interface does not tell you anything about the
exceptions that might be thrown. If it matters, you will have to
look at the implementation. Conversely, the place where exception
specifications matter the most is on those functions for which
the implementation is not available.

The point here is not to get
caught up trying to figure out the exception specification for
every function. Remember, there is a lot of legacy code, both C
and C++, which works just fine without exception specifications.
Most of this code does not throw exceptions; in fact most of it
was written before exceptions were a possibility. Still, it would
be incorrect to give such functions a throw()
specification without also rebuilding the libraries. Otherwise
the functions do not contain the runtime enforcement that is
guaranteed. This could be a problem because changes in underlying
libraries can alter the exception behavior of legacy code. The
fact that operator new() will now throw a bad_alloc exception
by default instead of returning a null pointer is going to affect
a lot of existing code (programmers can still get the old (or
current) behavior if they desire, but they will have to change
code to get it).

Guideline 3. - Reserve
a specification of throw() for only the most trivial
functions.

The problem with parse_it()
above was that the exception specification of throw() was
really premature, even though it may have been correct at the
time it was written. It does not even take a change to parse_it()
itself to invalidate the exception specification, just relinking
with a different version of a library. In reality, just about any
non-trivial function is likely to have some possibility of an
exception. Usually, this is bad_alloc (or some
derivative), but not always. As noted above, getting exception
specifications wrong is not a good idea (remember that death
penalty), and removing or changing exception specifications
later is also extremely problematic.

Guideline 4. - Include bad_exception
in any exception specification for any non-trivial function.

While our goal is not to have
unexpected exceptions in our programs, we have to accept reality.
Sometimes code gets released before all the possibilities have
been tested. Another reality is that we are moving towards a
world where dynamic linking and object request brokers allow
portions of a system to change independent of the others. It is
all too likely that a new version of a library may throw
exceptions where an older version did not. If exceptions are the
rare events they are suppose to be, this new version may be
installed and running with our code for some time before anyone
discovers the fact that there is now the possibility of an
exception where there was not one before.

We can protect ourselves and our
clients from our own mistakes by making sure our exception
specifications include bad_exception. The program that
truly can not afford to abort can replace the default unexpected_handler()
with a version that will throw a bad_exception. The rest
of the program would be designed to cope with a bad_exception
exception. Again, note that having bad_exception in the
exception specification does not mean that the code will throw a bad_exception
automatically -- the default behavior of an exception
specification violation is still to call terminate().
Specifying bad_exception however, gives our clients some
flexibility -- they can let an unexpected exception terminate the
program, or they can arrange things so that the unexpected
exception is converted into a bad_exception. We will see
below how this might be done in a way that does not leave the
program totally in the dark about what exception really occurred.

I also think that including bad_exception
in an exception specification is a hint to the human reader that
the rest of the exception specification is not a total guarantee.
In other words, other exceptions besides the ones stated in the
specification may be possible, but we don't know exactly what
they are. We can guarantee that they won't slip out of the
function, but not that they won't happen. Then it is up to the
user to decide whether the default method of dealing with such an
unknown exception is acceptable or not.

Guidelines 1-4 are pretty general
purpose. Now let us turn to some specific situations with some
specific exception specification guidelines.

Guideline #2 may be generally
applicable to general purpose reusable libraries, but there are a
couple of specific places where we want to avoid exception
specification even though they would otherwise seem like an
appropriate place for them. One place where we do not want
exception specifications is on template functions. This includes
all functions defined as part of a template class.

More specifically, we do not want
exception specifications on any template functions that are
instantiated with class types and invoke operations on objects of
the instantiating type. The type used to instantiate the template
is unknown, so the exceptions that it might throw are also
unknown. This makes writing valid exception specifications for
the template function rather difficult.

It is better to consider
templates to be adjuncts of the classes used to instantiate them.
Leave the specifications off the templates, and let the user, who
knows what the instantiating type is, deal with the exceptions
thrown by that type.

The problem with templates is
that we do not know what exceptions can be thrown by the types
used to instantiate the template. The problem with virtual
functions is that we do not know what exceptions can be thrown by
the actual function called via a virtual function call.

The language specification
attempts to deal with this by requiring that an overriding
virtual function has to have an exception specification at least
as restrictive as its base class version. For example, if the
base class specifies

virtual void foo()
throw(logic_error);

then a derived class can declare

void foo() throw(logic_error);

which is the same as the base
class, or

void foo() throw();

which is more restrictive, but it
can not declare

void foo();

which is less restrictive than
the base class version. Nor can it declare

void foo() throw(logic_error,
bad_alloc);

As this example shows, the base
class function apparently does not use the free store because it
does not include bad_alloc in its exception specification.
This means that no overriding function can use new or any
function that might throw a bad_alloc unless they are
willing to handle the exception within the function itself. This
can be very aggravating to the programmer who is trying to
develop a derived class.

When I first wrote this, I would
have said that virtual functions should not have exception
specifications at all. I have learned that this is not considered
acceptable by those who feel that the base class specifies the
class interface and that all functions of a base class need
exception specifications. I do not necessarily agree, but I am
maintaining a wait and see attitude.

One way to sidestep the issue is
for a class to define its own exceptions (see Guideline #3 in
[2]). Then the exception specifications can at least contain the
library exception base class, as in:

virtual void foo()
throw(LibraryException);

This allows (ne. forces) the
developer of a derived class to derive another exception from the
LibraryException base class if one is needed. The derived class
could declare

void foo()
throw(LibraryBadAlloc);

and as long as LibraryBadAlloc
was a publicly derived class of LibraryException, then
things would work.

This is one guideline that is
going to have to be applied after carefully considering each case
individually. There will certainly be situations where the
functions of a base class should have exception specifications,
and derived functions will be expected to conform to those
specifications. This is probably the case when a base class
supplies its own exception base class like this example. This
guarantees that users are able to write exception handling code
around the exceptions thrown by the base class library and be
sure that they also will deal with exceptions thrown by derived
classes. An overriding function can derive a new exception from
the exception base class, if needed.

On the other hand, many classes
represent simple abstractions, even if they are abstract base
classes. It does not make sense to define a separate exception
class for such classes and clutter the interface with exception
specifications that compound the problem of creating the concrete
derived classes. Remember, those who derive from your class
libraries are your clients just like those who use your
libraries. You want to avoid placing arbitrary restrictions on
how your class library can be used. If you place exception
specifications on your virtual functions, you are declaring more
than just an interface -- you are imposing restrictions on the
implementations. Try to avoid this, if possible.

A corollary to this guideline has
to be:

Corollary #6: never
put a specification of throw() on a virtual
function.

In general, we want to give
clients of our code, including those who derive from our classes,
as much flexibility as possible. Too restrictive exception
specifications on virtual functions deny this flexibility.

This is not so much a guideline
as a problem. Often typedefs are used to define synonyms
for a function pointer that can then be used in parameter lists
for defining callback functions. Callback functions are a fact of
life in large programs, whether they are done with function
pointers or function objects. This is one area where exception
specifications seem justified -- the tighter the better.

This is because callback
functions are often called from deep within the processing of a
library to provide for some tailoring of response. Some callbacks
are very specific in the functionality they are suppose to
provide, others can do almost anything at all. In all cases,
exceptions thrown from the callback are going to be unwelcome.
Ideally, we write our code so that exceptions from callbacks can
be safely propagated all the way back to the user who originally
supplied the callback, and who presumably has some idea of how to
handle the exception. This may not be very realistic, so we may
want to insist that any callback function the user supplies to us
does not throw any exceptions that we can not handle. This is
what exceptions specifications are for -- right?

It would seem like it. The
language specification says that if I write

void call_it(void
(*callback)(int) throw(bad_alloc));

then any function pointer used to
initialize the parameter callback must have an exception
specification at least as restrictive as that specified in the
parameter. Unfortunately, the language specification also says
that I can not write this

typedef void (*Callback)(int)
throw(bad_alloc); // error

void call_it(Callback
callback);

because exception specifications
are not allowed on typedefs. This seems like a real shame, and
unfortunately I haven't got a clue as to why this restriction
exists. The only obvious thing to do about it is to use the first
form for declaring function parameters instead of the typedef.

We wrap up with some
miscellaneous guidelines that are peripherally related to
exception specifications. The Standard provides two functions for
getting at the insides of the exception handling mechanism
itself: unexpected_handler() and terminate_handler().
You have to wonder just how much real use there will be in
overriding the default versions of these functions. Nevertheless,
they exist and if I can think of even one reason to use them, I'm
sure others will have dozens of reasons.

Guideline 8. - Change terminate_handler()
only at the application level.

I think unexpected_handler()
is potentially more useful than terminate_handler(), so I
will deal with terminate_handler() first. A user can
replace the default version of terminate_handler() by
passing a function pointer in a call to set_terminate(). A
user supplied version of terminate_handler() can not do
anything except exit the program, however. As a general rule, terminate()
(which calls terminate_handler()) is called when the
exception handling mechanism gives up, so this makes some sense.
It therefore also makes sense that only the very highest level
routines of a program have any business changing the way the
program terminates. Certainly, internal libraries should just
depend upon the fact that a call to terminate() will end
the program and not worry about how it happens.

I will mention one possibility:
the default terminate_handler() calls abort().
Calling abort() is not a nice way to end a program. A user
supplied version of terminate_handler() might call exit()
instead. Calling exit() will allow any functions supplied
in a call to atexit() to run, and will also invoke the
destructors of all static objects. Function exit() still
will not unwind the stack however, so if terminate() is
called from unexpected_handler() somewhere down in your
program (or because of an exception during a stack unwind), your
program is not going to exit cleanly, no matter what. When I say
"exit cleanly", I mean that all object destructors are
invoked and all resources are properly released as a result. This
is what a stack unwind is suppose to do, and what will not happen
if the program terminates without unwinding the stack. Still, a
program may be able to do some clean-up with one or more special
purpose atexit() functions, so calling exit() from terminate_handler()
instead of abort() may help somewhat.

As I mentioned in [2], cleanly
terminating a C++ program means completely unwinding the stack,
and then making sure that all global and static objects are
properly destroyed. The only guaranteed way to do this is to return
from main(), which may not always be convient. In [2],
Guideline #9 offers a solution to this problem -- "Throw
a special exception to end the program, if you must".
It may occur to you that instead of catching the special
exception in a function-try-block of main() and returning
(as I show in the guideline in [2]), you could just replace terminate_handler()
with a version that calls exit() and get the same effect.
After all, if an exception propagates out of main() then terminate()
will be called.

There is one slight problem with
this idea. The purpose of throwing the special exception was to
unwind the stack, (which neither exit() nor abort()
will do), while the return (which calls exit()) in
the catch clause of main() was there to invoke destructors
of static objects (which abort() will not do). The problem
with letting an exception propagate completely out of main()
is that the (draft) specification leaves it up to the
implementation whether it unwinds the stack before calling terminate()
in such a case. Some may do so, but others may decide that not
unwinding the stack makes more sense from a debugging standpoint.
You have to catch an exception to make sure the stack unwind
takes place. You also have to propagate the exception to main()
to unwind the stack.

Guideline 9. - Make
sure you restore unexpected_handler() if you change it
below the application level.

Since unexpected_handler()
(and terminate_handler()) is global to the entire program,
if you change it at one level, you are overriding any version
that might have been set by a higher level. Since it is the
highest levels that are the most likely to replace the default
version, care must be taken if unexpected_handler() is
reset at a lower level. Make sure that you save the pointer
returned by set_unexpected() (or set_terminate()),
and restore it when your routine exits. Remember the Golden Rule
of Exception Handling (Goal #1 in [1]: "When you
propagate an exception, try to leave the object in the state it
had when the function was entered.") and make sure this
replacement is handled in an exception safe manner.

Guideline 10. -
Unexpected_handler() should either throw bad_exception
or throw a valid exception.

If you write your own unexpected_handler()
(for more than debugging purposes), then you presumably are
expecting an unexpected exception (you know what I mean). Maybe
you are mixing two libraries that use different exceptions for
similar things (e.g. xalloc and bad_alloc for a
memory allocation failure). Or maybe you have a callback function
that can throw an exception of a certain type, but you want to
use it in a case where there is an exception specification that
does not allow that specific exception type.

So you write an unexpected_handler(),
call set unexpected() to install it, call the potentially
erroneous lower level routines, and then restore the old unexpected_handler()
when you are done. If an exception of the wrong type hits an
exception specification that blocks it, your unexpected_handler()
will throw an exception of the right type and all will be well.

Unfortunately, there are no
parameters to unexpected_handler() nor is there any way
defined in the Standard to determine the type of an exception
that has been thrown (other than to catch it by type). So how do
you know if the exception that invoked unexpected() is
really the one you wanted to replace? Consider this work-around:

void my_unexpected_handler()

try {

throw; // rethrow the
exception

}

catch (xalloc& ex) {

throw bad_alloc(); //or throw
bad_exception();

}

catch (...) {

// throw bad_exception; or
terminate();

}

This little trick will allow an unexpected_handler()
to at least make some intelligent choice.

In general, an unexpected_handler()
has to be pretty generic. The most generic thing possible is to
call terminate(), which is what the default version does.
The next best thing is to throw bad_exception. This stands
some chance of making it past a well written exception
specification. In turn, this will give a higher level routine
(like the one that installed the unexpected_handler()) a
distinct exception to catch and handle. Since this routine
presumably has some idea of what possible exception could cause
the bad_exception to be thrown, this might be sufficient.
The last possibility is to throw an exception of some specific
type known to work. None of these techniques is very satisfying,
but neither is an unexpected exception that aborts the program.

CONCLUSIONS

As this article and its two
predecessors have discussed, using exceptions effectively in C++
can take a lot of work. These guidelines are a beginning, but it
is still going to take a lot of experience before developers
learn to think in terms of exceptions. Nevertheless, I am
reasonably confidant that will happen, and when it does, we will
have taken another step along the path towards better software.