"The (B)Leading Edge"
Guidelines for Using Exception Specifications

by

Jack Reeves
1996 - The C++ Report
April 15, 1996

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

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:

  1.  
  2. force any function that called a legacy function to also have no exception specification (essentially destroying their usefullness).
  3.  
  4. 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.
  5.  
  6. 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:

  1.  
  2. under certain circumstances the function might fail and propagate an exception of one of the types indicated back to the caller.
  3.  
  4. 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():

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 5. - Do not put exception specifications on template functions.

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.

Guideline 6. - Put only general exception specifications on virtual functions.

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.

 

Guideline 7. - Don't try to put exception specifications on a typedef.

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.

References

1. Jack Reeves, "Using Exceptions Effectively: Coping With Exceptions", C++ Report, Vol. 8, No. 3, March 1996.

2. Jack Reeves, "The (B)Leading Edge: Guidelines for Throwing Exceptions", C++ Report, Vol. 8, No. 5, May 1996.

3. Tom Cargill, "Exception handling: A false sense of security", C++ Report, Vol. 6, No. 9, November/December 1994.

4. Harald M. Mueller, "10 Rules for Handling Exception Handling

Successfully," C++ Report, Vol. 8, No. 1, January 1996.