This is an updated version of an article that appeared in the March 1996 issue of the °C++ Report.

Coping with Exceptions

by °Jack W. Reeves

In April 1995 the °ANSI / °ISO C++ committee released the Committee Draft of the C++ Standard. This draft freezes the feature set of the language and the standard libraries. In the Standard, Chapter 15 - Exception Handling takes only 7 pages. Of all the language chapters (1-16), only Chapter 1 (General: 6 pages), Chapter 4 (Standard Conversions: 4 pages) and Chapter 6 (Statements: 7 pages) are less than or equal in length. One might conclude from this that exceptions are an easy concept to specify and that there are not a lot of subtle details that need to be understood. Looks can be deceiving.

When I started to use exceptions, it rapidly became apparent that exceptions are much more difficult to use effectively than it first appears. In fact, the more I explored how exceptions can interact with non-exception handling code, the more convinced I became that exceptions may be the most difficult feature of C++ to use. I identified 3 reasons for this difficulty:

It is the first of these that is the real problem. When using other features of C++ such as classes, overloaded operators, virtual functions, or templates, it may be difficult to write the code correctly in the first place, but once written it is easy to use from then on. Exceptions go against this trend. Throwing an exception is easy; writing the code that uses the function that throws the exception is the hard part. Every function that can have an exception propagate out of it must be designed to propagate that exception correctly. As Tom Cargill says in Reference 1, "The really hard part of using exceptions is to write all the intervening code in such a way that an arbitrary exception can propagate from its throw site to its handler, arriving safely and without damaging other parts of the program along the way."

Of course, it is (ii) and (iii) that make writing all that intervening code difficult. Consider the apparent asynchronous nature of exceptions (exceptions are not truly asynchronous events like a network driver interrupt, but from the outside, they can have almost the same effect). The following statement:

is a typical C idiom that has been carried over (with some major extensions) into C++. In fact, one can almost say that the °Standard Template Library was designed around such idioms. In C++, i and j can be two different, user-defined iterator types. Assume that i iterates over a container of A objects, and j iterates over a container of B objects. Let's let a represent *i and b represent *j. Assignment will have been overridden if A is a user defined type, but it may not be defined specifically for the type represented by B, necessitating invocation of a user-defined conversion function. Another user-defined conversion function may have to be invoked to convert the result of the assignment statement into something that can be converted to type bool for the "while" condition. Any of these user defined operators or conversions might throw an exception — or any of the functions that they invoke, and so on. If an exception occurs in this statement, the user may have no idea where it came from. More importantly, the order of evaluation of several parts of the above expression is unspecified. Therefore, if an exception appears, the user may have a hard time determining the current state of i, j, a, or b.

Finally, as noted in (iii), exceptions can not be ignored. With older error reporting techniques, nothing but discipline forced the programmer to check the return value or error code. Even if you knew errors were possible, you did not have to check the return value immediately, just as long as it was checked before it mattered. With exceptions, this is no longer possible. You have to cope with an exception when and where it occurs, not when and where it is convenient for you. This is true even if you do not plan to actually handle the exception.

So, I have concluded that using exceptions effectively is going to be difficult. Nevertheless, I have also considered the alternatives. Without exceptions it is impossible to return an error from a constructor or an overloaded operator. This situation has lead to several ad-hoc schemes. A couple of the more popular ones are:

Since there is no standardization for any of this, it is difficult to combine libraries that use slightly different techniques. It also is virtually impossible to use such classes to instantiate general purpose templates like those that are now part of the °Standard C++ Library. The truth is: without exceptions, a lot of the features that make C++ so powerful — constructors, overloaded operators, and templates — are either not as robust as we need them to be, or simply can not be used in situations where good error handling is required.

What follows are some goals and guidelines that I have put together on how to cope with exceptions. The "goals" are general, high level concepts. Meeting a "goal" may involve re-writing a function or major changes to a class library. The "guidelines" are more specific, lower level concepts that can often be applied to help meet the goals. As with most such offerings, every user will have to evaluate their own circumstances and determine which of these goals and guidelines are applicable to their situation. In many cases, I have found that exceptions are already approaching the status of religion. Under the circumstances, I try to offer reasonable explanations for my recommendations. Nevertheless, my ultimate goal is more reliable software, not religious discussions. If other approaches work for you, by all means use them. I would like to hear from readers who have applied these and other guidelines on real projects — successfully or not. The more collective knowledge we can build on this topic, the better off we will all be.

Terminology and Conventions

In the following, I often mention the "state of an object". In Reference 2, Dr. Harald Müller presents a more general discussion of the problems I discuss in goals I through III. In his article, Dr. Müller talks about "resources". I have decided to stick with the term "object" since that is the type of resource most C++ programmers are going to be concerned with most often (I do use the term "resource leak"). In Dr. Müller's article, he defines and discusses how resources can be in either "good" or "bad" states. While my concepts are similar, I prefer slightly different terminology than Dr. Müller. In simple terms, an object can be in one of the following states:

My definitions correspond fairly closely with Dr. Müller's as follows:

Dr. MüllerMine
goodgood
bad (valid shut-down)bad
bad (not a shut-down)undefined

I differ primarily with Dr. Müller over the question of what operations are valid when applied to an object that is not in a good state. In the real world of typical C++ software, an object in an inconsistent or bad state may still be quite usable and the program in which the object is embedded may continue to run correctly.

For certain examples in the rest of this article, I use the same Stack template class as Dr. Müller uses in his paper. The original version of this template was presented by Tom Cargill in Reference 1. The interface for this class is shown in Listing 1. While the Stack class is fairly simple, it remains a good example of the difficulties inherent in using exceptions.

A couple of brief notes about coding style:

  1. I prefer to use the constant symbol null to represent the null pointer in my code instead of the current convention of using 0. I ask the reader to assume the following declaration exists within the scope of all the code that follows:
  2. I have adopted a commenting convention of //>x to indicate places in a function where exceptions are possible. Like any manual approach, this technique is error prone and very likely to miss something. It is better than nothing, however, and I have found it useful.

The following table summarizes the goals and guidelines that are presented in the next section.

Table: Goals and Guidelines
I. When you propagate an exception, try to leave the object in the state it had when the function was entered.
  1. Make sure your const functions really are const.
  2. Perform exception prone operations early, and/or through temporaries.
  3. Watch out for side effects in expressions that might propagate exceptions.
II. If you can not leave the object in the same state it was in when the function was entered, try to leave it in a good state.
  1. Either reinitialize the object, or mark it internally to indicate that it is no longer usable but might be recovered.
III. If you can not leave the object in a "good" state, make sure the destructor will still work.
  1. Do not leave dangling pointers in your objects. Delete pointers through temporaries.
IV. Avoid resource leaks.
  1. If you have raw data pointers as members, initialize them to null in the initializer list of your constructor(s), then do necessary allocations in the constructor body where a catch block can deal with potential resource leaks.
V. Do not catch any exception you do not have to.

  1. Rewrite functions to preserve state, if possible.
  2. Always use a catch (...) block to cope with propagating exceptions.
  3. If you get stuck, call terminate().
VI. Do not hide exception information from other parts of the program that might need it.
  1. Always rethrow the exception caught in a catch ((...) block).
  2. Rethrow a different exception only to provide additional information or capability.
  3. Make sure one catch block does not hide another.

VII. Never depend upon destructors for functionality in any situation where fault-tolerance is required.
  1. Do not throw exceptions from a destructor body.
  2. Do not arbitrarily handle all exceptions propagating from a destructor.
VIII. Do not get too paranoid.

Catching and Propagating Exceptions

We must state some assumptions before we actually start to deal with exceptions. We will assume that an exception thrown by a lower level routine indicates that the routine failed. In a follow-on article [Reference 3], I present some goals and guidelines for throwing exceptions. While there is no way to ensure that exceptions are only used to indicate a failure condition, (and there are a couple of situations where they may not), it is probably safe to assume that most programmers are not going to accept the overhead inherent in an exception throw (see Items ONMOUSEOVER = "self.status = 'M12'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">M12 and ONMOUSEOVER = "self.status = 'M15'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">M15) if other mechanisms are available to indicate normal completion.

We will also assume that exceptions are typically a rare occurrence in a well designed program. This means, in general, that the condition causing the error is not likely to be one that is easily anticipated and handled by the next higher level routine. The entire exception handling mechanism is designed to pass information from the point where an error is detected up the calling chain to a higher level of the program where enough information exists to be able to handle the error. In many cases, exceptions are going to propagate all the way to a human operator. Therefore, it is probably safe to say that the vast majority of code will simply propagate exceptions from lower level routines to higher level ones.

It is this propagation of exceptions that causes the grief and anguish. The function that throws the exception knows what state it is in and it chooses when to throw the exception. The function that finally handles the exception remains in control — it must necessarily branch into a separate exception handling path — but in handling the exception it recovers from the error and continues. All of the intervening functions between the one that originally threw the exception and the one that finally handles it are a problem. When an exception propagates out of a function, it indicates that the function failed. It is as if the function threw the exception, but without any control over where the throw occurs. The propagating exception terminates the function, unwinds its stack, and continues on its way.

If not anticipated and handled correctly, this propagation of an exception can leave dangling resources, bad or even undefined states, and in general cause more problems than the original error. Propagating exceptions destroy otherwise perfectly rational flow of control through a program. In a very real sense, exceptions are "goto's from hell". So how do we cope with them?

Goal I. When you propagate an exception, try to leave the object in the state it had when the function was entered. This is the Golden Rule of exception handling. It is not going to do much good for a program to handle an exception if the program has gone into an invalid state as a result of the exception propagating to the point where it could be handled. Writing code that meets this goal is very difficult. In truth, it may often be impossible. Nevertheless, I put it first because it is the target that should always be the ultimate goal. How to go about this is a complicated subject, and there are more detailed articles on dealing with it [Reference 2]. I will only give a few simple guidelines here.

Guideline 1. Make sure your const functions really are const. The first step in making sure objects stay in good states is determining which operations on the object actually change the state. The C++ language assists us in this regard by requiring member functions that can be applied to constant objects to be designated as const functions (see ONMOUSEOVER = "self.status = 'Item E21'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E21). As we all know however, const can be subverted, either by designating some members mutable (again, see ONMOUSEOVER = "self.status = 'Item E21'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E21), or with a const_cast (see ONMOUSEOVER = "self.status = 'Item M2'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M2). If your const functions really do not change the state of the object, then you can safely ignore them — exceptions that propagate from them will not affect anything.

On the other hand, if your const functions do change the state of the object, then you must treat your const functions like your non-const functions — only more so. As a user, I would be very annoyed if I discovered that a const function could actually change the object into a bad state under some circumstances. If you discover such a function in your class, I would urge reconsidering whether it should be a const function. An example of this actually occurs in the ONMOUSEOVER = "self.status = 'Standard C++ Library'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Standard C++ Library. The basic_string function c_str() is a const function, but the specification is clear that an implementation can reallocate the internal character array if necessary to make room for the terminating NULL character. This could throw a bad_alloc exception. When a const function exhibits such behavior, it has numerous ramifications, most of them bad.

Guideline 2. Perform exception prone operations early, and/or through temporaries. Consider the Stack::push function:

Each of the comments indicates a statement that potentially can throw an exception. For example, the statement at x1 can throw a bad_alloc exception if there is not enough memory to satisfy the request. In this statement, the member variable nelems_ is increased, and then used as the size of the new request (a typical C idiom). If this request fails and throws an exception, it propagates out of push() leaving nelems_ set to the new value. The Stack object is now in an bad state.

Besides doing memory allocation, statement x1 also uses T::T(). Similarly, statements x2 and x4 use T::operator=(), and x3 uses T::~T(). Each of these functions might throw an exception. For now I will ignore the possibility of exceptions from destructors, but x1, x2, and x4 are all potential problems. If we propagate an exception from one of these operations it will indicate that the push() operation failed, but in what state will we leave the Stack object?

An exception caused by a failure in T::T() at x1 has the same effect as a bad_alloc exception. If we use a temporary variable for the new value of nelems_, then an exception from x1 can safely be allowed to propagate. We will assume that if T::T() throws an exception the runtime mechanism will destroy all the already constructed elements in the array and release the allocated memory. (Except in pathological cases, which are discussed below, the standard guarantees this behavior.)

If one of the assignments in the loop at x2 fails, the internal state of the Stack object is still good, but we have a memory leak since the buffer pointed to by new_buffer will never be deleted. We will discuss resource leaks in more detail under Goal IV below.

If the assignment at x4 fails, variable top_ will already have been incremented. In this case, the exception will indicate that the push() failed, but the internal state of the object will have been updated as though it succeeded. Again, we are in a bad state.

In order to make sure that possible exceptions that propagate out of push() leave the object in its original state (and do not cause a memory leak), a number of modifications are necessary:

  1. Use a temporary for the new array size.
  2. Use a temporary object of the template class auto_array_ptr (see below) to create an automatic variable that manages the new buffer.
  3. After the new buffer is initialized, swap ownership of the new buffer between the auto_array_ptr object and the stack.
  4. Delay the increment of the top_ variable until after the new element is assigned to the stack buffer.

Class auto_array_ptr is similar to the auto_ptr template class defined in the utilities section of the C++ Standard Library (See Items > 9 and > 28). An auto_ptr is an object that owns a pointer. The destructor for auto_ptr deletes the object pointed to. My definition of auto_ptr is shown in Listing 2. The header for auto_array_ptr is shown in Listing 3 (its definition is almost exactly the same as for auto_ptr except that it calls delete[] on its pointer instead of delete). Using an auto_array_ptr object will solve our memory leak by automatically deleting the array if an exception occurs. When the new array has been initialized with copies of the elements from the old buffer (an exception prone task), the ownership of the new buffer is assigned to the stack object. Lastly, we re-write the assignment at x4 so that we do not update the state of the object member top_ until after the potentially dangerous assignment has completed. All this gives us:

The statement at line 3 swaps ownership of the old buffer and the new buffer. After the statement, the auto_array_ptr object owns the old buffer and the Stack object owns the new buffer (via its member v_). The new_buffer object will then delete the old buffer when it is destroyed at the end of the block. I do it this way so that if an exception occurs when the old buffer is deleted, the stack object will still be in a good state.

Even this simple example illustrates a key difficulty in coping with exceptions. When writing a function, it is necessary to decide which operations might cause exceptions and which operations are exception-safe. Exception specifications (which are discussed in a later article [Reference 4]) can help in this task, but they are not a complete answer (see ONMOUSEOVER = "self.status = 'Item M14'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M14). In particular, template programmers do not have access to the exception specifications of classes used to instantiate the template. (For a further discussion of this topic, consult Herb Sutter's article, "Exception-Safe Generic Containers"). Once you get into the mind set of expecting exceptions, the problem often becomes trying to determine which statements are NOT possible sources of exceptions. The only operations that can safely be assumed to never throw exceptions are the basic operations on the built-in data types. In the example above, the call to auto_array_ptr::reset() is safe because all it does is swap two built-in pointer types through a temporary.

The auto_array_ptr object automatically deals with the possible exceptions in the loop at line x2. An exception prior to line 3 will now propagate out of the function without causing either a bad state or a memory leak. That leaves only the possible exception from the assignment at line x4 to be dealt with, which brings up Guideline 3.

Guideline 3. Avoid side effects in expressions that might propagate exceptions. Side effects are a fact of life in C++. Some of them we can not see directly (and can not do anything about). Others we can see. In statement x4, whatever happens inside T::operator=() are of the former type, top_++ is one of the latter. As it was originally written, an exception from T::operator=() was guaranteed to leave us in a bad state (at least — the state might have been even worse, i.e. undefined) because top_ would have been incremented before T::operator=() was called. Since T::operator=() might throw an exception, we must avoid the side effect to top_ by postponing the increment.

Note that even with this change, we can not be sure what state the Stack object is in if an exception occurs at line x4 — it depends upon what state T::operator=() leaves the object at v_[top_]. If an exception thrown by T::operator=() leaves the T object in a good state, then we can say that the Stack object itself is in a good state. If the assignment leaves the T object in a bad state, then we must consider the Stack object to also be in a bad state — we can not expect another call to push() to work if the stack buffer now contains a bad object. (In this case, even though the stack itself is in a bad state, we can still pop() those objects that currently exist on the stack. In fact, as long as we do not try to push() an object into the slot that now contains the bad T subobject, we can still use our stack as if it were good, even though it isn't. This is often the case in real software systems.)

In the final case, if an exception leaves the T object in an undefined state, then we must consider the Stack object to be in an undefined state. This means that it probably will not be possible to destroy the stack. While there is no way to tell what state the object at v_[top_] is in if an exception occurs at line 4, we want to make sure that if class T adheres to Goal I, then the stack object does also. For a slightly more complicated example consider the Stack::pop() function. In its original form:

The return statement uses the copy constructor of the template class T. If T::T(const T&) throws an exception, it will again do so after top_ has been changed. The exception will indicate that pop() failed, but the internal state will have been changed as though it succeeded. While it might be argued that the stack is still in a good state in this case, it does violate our goal — we want to leave top_ as it was when we entered the function. Since we can not decrement top_ after the return statement, we must catch the exception and reset the state (we will also throw a more appropriate exception for the Stack empty condition):

The domain_error class is one of the Standard library exception classes (see ONMOUSEOVER = "self.status = 'Item M12'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M12). It is derived from logic_error, which is in turn derived from exception. As discussed in the second article of this series [Reference 3], library exceptions should derive from the standard exception classes. Class domain_error is a general purpose base class that indicates that the domain of the attempted operation is invalid. For this example, I chose to throw a domain_error object directly rather than derive a new, Stack specific, domain_error subclass.

Goal II. If you can not leave the object in a same state it had when the function was entered, try to leave it in a good state. In other words, even if we lose the old state of the object, we would like to be able to reuse the object. This is often perfectly acceptable with the assignment operator since we expect to lose the old value of the object when we assign a new value to it anyway. Consider the Stack assignment operator (in its initial form):

We have several possible exception sites in this function. As before, I will ignore the possibility of exceptions from the destructors invoked in x1. Line x2 uses the default constructor for class T, and line x3 uses T's assignment operator. We can (and will) re-write this function to meet Goal I, but for now observe that if an exception propagates from line x2 we are in an undefined state (worse than 'bad'). The old array has been deleted, but an exception at x2 means we failed to allocate its replacement. Pointer v_ is left dangling and if we try to destroy the Stack object,delete will be invoked on this dangling pointer. If we do nothing else, we want to make sure that we can always destroy the object (Goal III — which we will get to later). This shows just how easy it is for a propagating exception to leave an object so messed up that it can not even be safely destroyed.

If we get past line x2 and hit an exception in line x3 we are in a more interesting position. The Stack object is now actually in a consistent state, but since we only have a partial copy done, the object is not valid. The exception propagating from the function will indicate that the assignment failed, but at this point, since we have deleted the old stack data, we can not restore the object to the state it had before the function was called. The only valid things that can be done with this stack object are to destroy it, or reassign a valid stack to it. I emphasize the word valid. This is a case where the internal state of the object appears good, and hence the typical operations could be invoked and would appear to work, but the meta-state (for lack of a better term) is inconsistent. In this case, the stack object does not represent a valid last-in-first-out ordering of the objects on the stack because it was truncated in the middle of the copy of a valid stack.

As noted, we can fix the assignment so that it meets Goal I, but let us assume that we could not do that.

Guideline 4. Either reinitialize the object, or mark it internally to indicate that it is no longer usable but might be recovered. If the Stack object can not be restored to its original state, we want to make sure that any further attempt to use the object will be rejected. The user might handle the exception, but not realize that the object is no longer valid. In the case of our Stack assignment operator, we can simply reinitialize the object to the empty state. In more complicated cases, we may want to leave the object in the state it was in until the user explicitly clears the condition. This makes sure the user is aware of the problem. Our Stack::operator=() now becomes:

Line x1 deletes the array through a temporary (auto_array_ptr::remove() is discussed below) to guarantee that the object can be destroyed in case of an exception. Line x2 is now safe, and any exception will leave the object in a valid empty state. Likewise, an exception at line x3. As noted above, we can rewrite this function to meet Goal I, but this is a good start.

Goal III. If you can not leave the object in a "good" state, make sure the destructor will still work. While the ultimate goal is to leave an object in a good state (Goal I or II) it may not always be possible. As a last resort, we want to leave the object in such a state that it can be safely destroyed. Never forget that as an exception propagates it will unwind the stack frame of every function it propagates through. Many times this will invoke the destructor of the very object that threw the exception. One question that should always be asked when attempting to cope with exceptions is: "What will happen if this exception attempts to destroy this object?"

As our original Stack::operator=() function showed, it is not difficult to leave the object in a state where even the destructor will not work. The most common cause of this is a dangling pointer.

Guideline 5. Do not leave dangling pointers in your objects. Delete pointers through temporaries. In the Stack assignment function above, the simplest solution to the dangling pointer was to set v_ to null after the delete statement. Since I figure this is going to be such a common occurrence, I added a static function to my auto_ptr (and auto_array_ptr) class to facilitate this operation. Calling auto_ptr::remove(p) (auto_array_ptr::remove(p)) will delete pointer p through a temporary and leave p set to null. Even if an exception occurs during the destructor call, p will not be left dangling. If you use auto_ptr objects as members of your class instead of raw data pointers (see ONMOUSEOVER = "self.status = 'Item M10'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M10), the task becomes even easier. If ap is an auto_ptr object, then:

will set ap to null internally before returning the pointer so it can be deleted.

Goal IV. Avoid resource leaks. The most obvious type of resource leak is a memory leak, but memory is not the only resource that can leak (I once had a program that tended to leak TCP/IP sockets). In one sense, a resource leak is just another example of a bad state. In this case though, the resource that is in the "bad" state is the one that has leaked, not the one that did the leaking. For this reason, I deal with it separately.

There are three different instances where exceptions can cause resource leaks: in a constructor, in a destructor, and in a function (whether a member of a class or not). Let us look at constructors first.

Constructors are a special case for the exception handling mechanism. When an exception propagates from a constructor, the partial object that has been constructed so far is destroyed. If necessary, any memory allocated from the free store for the object is also released. Note that the destructor for the object itself is not called — only destructors for any completely constructed subobjects. If, during the construction, a resource (such as memory) is obtained directly and not as part of a subobject whose destructor will release the resource, then a resource leak can occur (again, see ONMOUSEOVER = "self.status = 'Item M10'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M10). For example:

If an exception is thrown by T2() during the initialization of p2_, then the T2 object will be destroyed, and the memory obtained by new will be released, but not the pointer held in p1_. We have a memory leak.

There are a couple of ways this can be dealt with. We could use a try block to catch the exception and attempt to release the memory, but if we have several resources allocated this way, then the nested try blocks can get tedious and error prone. An alternative is to make sure the pointers are initialized to null, and then delete them all in the catch block:

This example uses several features from the new Standard, so do not expect this to work on your compiler yet. Placing the try keyword immediately after the parameter list (or the exception specification if one exists), and the catch clauses after the function body, produces a function try block. A function try block associates a handler with the entire function body, including the constructor initializer list. In this example, the initializer list is not the concern, but you can see that the initializer list is within the scope of the try. With any pointer not set in the constructor body guaranteed to be null, the catch block can safely invoke delete on them. (The throw in the catch block is redundant. A catch clause of a function try block for a constructor or a destructor will automatically rethrow the exception when it finishes. As a matter of style, however, I always explicitly rethrow exceptions caught by a catch (...) clause (Guideline 10).) All this leads to:

Guideline 6: If you have raw data pointers as members, initialize them to null in the initializer list of your constructor(s), then do necessary allocations in the constructor body where a catch block can deal with potential resource leaks. This is one possible way to deal with a potential resource leak in a constructor. Another technique is to use the "resource acquisition is initialization" strategy. In this case, we make sure that every resource is associated with an object whose destructor will deallocate it. For dealing with memory allocated from the free store, the standard library template class auto_ptr (Listing 2) is available. Applied to our example:

Alternatively:

Now, since ap1_ and ap2_ are both objects, if an exception occurs trying to initialize ap2_, then the stack unwind will destroy ap1_, which will call delete on the allocated pointer. In this case, our destructor is empty since the destructors of member objects are invoked automatically.

Besides having a destructor that will delete the resource, template auto_ptr also provides functionality for safely transferring ownership of a resource. We have made use of this capability several times already. In this case, we can use an "acquire then transfer ownership" strategy to give us the following version of X's constructor:

The auto_ptr objects are used to acquire the resources in a manner that guarantees they will be deleted if an exception occurs. When all resources have been successfully acquired, ownership is transferred to the class itself. This is just Guideline 2 applied to resource acquisition. I see this as a transitional strategy for constructors, however. In the long run, I suspect that the use of raw data pointers as class members will diminish in favor of auto_ptr style objects. This simplifies maintenance as well as the problems of coping with exceptions. If you worry about performance, keep in mind that all the operations of auto_ptr are inline functions (see ONMOUSEOVER = "self.status = 'Item E33'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E33). Most of these are one line functions that any decent compiler should have no trouble handling.

Resource allocations in ordinary functions can also cause resource leaks. Whereas a constructor is building an object and the goal is to make sure everything already acquired is released if an exception occurs, a function usually obtains a resource for internal use and releases it upon completion. If an exception happens after the resource is acquired but before it is released, then we have a leak. Under the discussion for Guideline 2, we used an auto_array_ptr object in Stack::push() to manage the new buffer while it was being initialized. We can do the same thing in Stack::operator=():

If v_ were an auto_array_ptr object instead of a raw pointer we would use the Standard template function swap() to perform the exchange of ownership:

Finally, we can have resource leaks in destructors. As a general rule, we do not want to throw (or propagate) exceptions from destructors (see ONMOUSEOVER = "self.status = 'Item M11'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M11 and Herb Sutter's article on "Exception-Safe Generic Containers"). Nevertheless, we can not always prevent it, so let us take a look at a simple problem. Consider our first example of class X above. We have two pointers to two different types of objects. Everything has gone well, and now the destructor of X is invoked. It is pretty simple.

Not much to go wrong here, but assume that it does — say the T2 object throws an exception when deleted. Like constructors, destructors are special functions for the exception runtime mechanism. When an exception occurs in a destructor, it is treated like an exception in a constructor, i.e. all complete subobjects that still exist are destroyed (in reverse order of their construction) and then the memory deallocation function is called, if needed. (I assume this is what is suppose to happen. The Standard is not at all clear about how exceptions from destructors are handled. I make this assumption based upon the discussion of exceptions in the ARM (see ONMOUSEOVER = "self.status = 'Item E50'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E50), and because it seems the logical thing to do. It would not surprise me if compiler implementers disagree.)

Exactly the same problem occurs here as it does in a constructor. The exception from the destructor of T2 will terminate the body of the destructor without deleting the resource held by p1_. The solutions are likewise similar to those applied to a constructor.For example, we can attempt to catch the exception and guarantee that other resources are deleted. Unlike the constructor however, there is no way to organize things so we can cover everything in a single catch clause — at least not without more work than is worth it. Once again, we turn back to the "acquisition is initialization" strategy and use auto_ptr objects. If all our member variables are auto_ptr objects then our destructor is empty anyway, so let us assume otherwise.

This may seem a little silly, but it does work. We transfer ownership of the resources to the two temporary objects (we create them in the same order that p1_ and p2_ are declared so they will be destroyed in reverse order). When the destructor body exits, these objects are destroyed, deleting their pointers. If an exception occurs in the destruction of t2, then the stack unwind will still destroy t1.

Goal V. Do not catch any exception you do not have to. There is an old C rule of error handling (actually it probably goes back to the Countess Ada Lovelace) that states: "do not test for any error condition you do not know how to handle." Like most cynical proverbs, there is a certain amount of wisdom in this. In C++, we now have a standard way to handle any error condition — throw an exception — but this just shifts the burden of the problem. The basic truth remains: it is a waste of time (yours and the computer's) to catch an exception you do not know how to handle.

Guideline 7: Rewrite functions to preserve state, if possible. As we have seen in the discussions above, when an exception propagates upward from a lower level function, we may have to catch it just to reset the state of our object. The point of Goal V is that we want to avoid this as much as we can. Consider our rewritten push() function. The last two lines now read:

We deliberately moved the incrementing of top_ after the possible exception from the assignment operator. We could have written something like:

This is similar to what had to be done in the pop() function. In that case we did not have any choice; in push() we do. Goal V says that if we can arrange our functions so that we can avoid writing try/catch blocks then we should do so. It leaves us with cleaner code and it is more efficient to let the exception just propagate than it is to catch and rethrow it.

There is a potential downside with doing this that has to be noted — when we write a try/catch block, it is obvious that there is the possibility of an exception. When we write:

instead of

it is not clear that the first form is necessitated by the possibility of an exception. We run the risk that during maintenance, some experienced C programmer (but new C++ programmer) will take a look at the two lines and decide that whoever wrote them did not appreciate the compactness of expression possible in C++ and change them back into the latter. In some sense, this is one of the most annoying drawbacks to using exceptions in C++ — a great many of the cherished idioms from C are going to have to be discarded. Instead, we are going to have to use a much more deliberate (and verbose) style that allows us to maintain better control over the state of our objects in the presence of exceptions.

In order to avoid this maintenance problem, we must document every place in our code where we think an exception is possible, and especially everywhere we have changed code to make it exception safe. I use comments of the form //>x. This is not a particularly good solution, but it is better than nothing.

Guideline 8. Always use a catch(...) block to cope with propagating exceptions. If we follow Guideline 7 we will avoid catching exceptions if we can, but there will be times when it can not be avoided, or the work necessary to avoid it is excessive. If we conclude that an exception might occur, and that it could leave us in a bad state, then we want to make sure we catch that exception. Since we are not concerned with handling the exception, only with protecting the state of the object, then the type of the exception does not matter. Therefore we use a catch block with a single handler:

This is pretty obvious for a template class like Stack, where we have no idea what kind of exception might be thrown by T, but in general, even if we know (or think we know) what the actual exception type is, if we are not actually handling the exception, then we should use a catch (...) clause.

Guideline 9. If you get stuck, call terminate(). This is not really about handling exceptions, but it does have something to do with coping with errors. There are places where throwing an exception does not make sense, and the only possibility left is to end the program. An obvious circumstance where this happens is in a user defined version of unexpected_handler. Unexpected_handler must either throw an acceptable exception, or end the program.

Before exceptions, the normal way to abnormally terminate a program was by calling the C library function abort(). In the new C++ world, we should call terminate() instead. terminate() just calls terminate_handler. In the default case, this calls abort(), but the user can replace the default terminate_handler with a program specific version. So call terminate() to allow any user defined terminate_handler to run.

Goal VI. Do not hide exception information from other parts of the program that might need it. Just to reiterate the obvious: the purpose of exceptions is to pass information from the point where an error is detectable to a point where the error can be handled. If you throw a different exception rather than rethrowing the original exception, you want to make sure you are increasing the information content by doing so.

Guideline 10. Always rethrow the exception caught in a catch (...) clause. I will predict that this will probably be the most common exception handling mistake. Once you have reset your state in a catch (...) clause, you want to be sure that you rethrow the exception so that higher level routines will have a chance to handle it. A failure to rethrow the exception means you have "handled" the exception — which is probably not what happened at all. Just for a reminder, the statement to rethrow an exception is:

  throw;       // see  ONMOUSEOVER = "self.status = 'Item M12'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M12 for details

Guideline 11. Rethrow a different exception only to provide additional information or capability. If forgetting to rethrow the exception from a catch (...) clause is likely to be the most common exception handling mistake, rethrowing a different exception will probably be second. Consider the following:

At first reading this seems to make sense. Foo() invokes some operation that might throw a private exception type, which is caught and handled. To be on the safe side, foo() includes a catch-all block just to cover the bases. The problem is that when foo() throws the generic exception in the catch-all block, it effectively destroys all the information in the original exception. This information presumably indicated what the original error was and might have been of use to a higher level routine that was willing and prepared to deal with an error of that type. Now, all that propagates upward is a self-fulfilling prophecy, and the program will probably terminate because nobody knows how to handle an "unknown exception".

In our ongoing Stack example, there are several places where we use the new statement. Under the C++ Standard, new will throw a bad_alloc exception if the memory allocation fails. In the second part of this series of articles [Reference 3], we add a private exception class to Stack that is derived from bad_alloc. In Stack<T>::AllocationError we include some additional information besides that available from bad_alloc alone, including the new size of the stack being allocated.

In order to be able to throw Stack<T>::AllocationError we use the nothrow() placement form of new (see ONMOUSEOVER = "self.status = 'Item E7'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item E7) as in:

Class nothrow is defined in the Standard header file <new> along with the placement form of operator new used above. When this form is used, operator new reverts to the traditional behavior and returns a null pointer if the allocation fails. This is the preferred way to do this. Note that this form only eliminates the possible exception from operator new, an exception can still be thrown by the constructor of the object in the new expression. For that reason, we want to be careful NOT to do the following:

In this case, our catch (...) clause will not only catch the bad_alloc exception as the (erroneous) comment indicates, but we will also catch any exceptions thrown by the T::T() constructor. If the new expression fails because of an exception in the latter, we will hide that exception with our version of bad_alloc. While it is true that either case will indicate that the function failed, hiding the original exception means that any higher level routine that handles the Stack<T>::AllocationError exception will be solving the wrong problem.

Guideline 12. Make sure one catch block does not hide another. When the exception runtime attempts to locate a handler for an exception, it tries the handlers in order. Unlike function overloading, where the best match from a set of possible functions is found, an exception handler is found on a first match basis (see ONMOUSEOVER = "self.status = 'Item M12'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M12). This means that you always want to put more specific handlers before more general handlers. Handlers specifying base classes (or references to same) must come after those for more derived classes. Obviously, any catch (...) clause must be the last handler of a sequence. The compiler will almost certainly complain if the catch (...) clause is not the last one, but it may not generate an error message for other incorrect orderings, so beware.

Goal VII. Never depend upon destructors for functionality in any situation where fault-tolerance is required. As a general rule, we do not want exceptions propagating from a destructor (see ONMOUSEOVER = "self.status = 'Item M11'; return true" ONMOUSEOUT = "self.status = self.defaultStatus">Item M11). There are two reasons for this. First, there is simply a semantic problem with deciding what it means for a destructor to have an exception. For ordinary functions, an exception typically means the function failed. What does it mean when a destructor fails? Certainly, it should not mean that the object is left in a usable state since it was not going to be usable if the destructor succeeded. In most cases the memory for the object will disappear anyway, whether the destructor succeeds or not. In other words, there is not much we can do about handling an error that occurs in a destructor, and we ordinarily want the destructor to finish anyway, so there is not much point in signaling errors from a destructor, even though exceptions give us a way to do this.

The second reason for avoiding exceptions in destructors is more fundamental - they are likely to abort the program. In a sense, destructors are part of the exception handling mechanism itself. Destructors are invoked by the exception handling runtime as part of the stack unwind process. If a destructor throws an exception during an exception stack unwind, then the Standard says the exception handling mechanism gives up and calls terminate(). For this reason, other writers have taken the position that destructors should never throw or propagate exceptions [Reference 2, Reference 5]. I do not go quite that far, but the following guidelines should apply under most circumstances.

Guideline 13. Do not throw exceptions from a destructor body. This is the principal behind Goal V again. Clearly, there is no sense in testing for error conditions in a destructor body unless they can be handled right then and there — otherwise ignore them (or call terminate()).

Guideline 14. Do not arbitrarily handle all exceptions propagating from a destructor. Exceptions can still legitimately occur when a destructor calls an ordinary function that throws an exception. It is tempting to suggest that destructors should not call such functions, or to say that every destructor should handle its own exceptions — tempting, but unrealistic. The first is impractical, and the second leads to constructs such as

  catch (...) {}

which I object to on aesthetic grounds. On the other hand, are there ever legitimate occasions when we might want to propagate an exception from a destructor? Consider the following real-world problem:

A program captures medical images from a Computed Radiography (CR) scanner and transfers them over a fiber optic network to a central image server.If there is any problem with the network, or with the image server, the program is required to save the image to local disk, generate a message on both the local console and the system administrator's console (a separate network is available for system administration tasks), and enter an error state that prevents any more images from being captured until the problem is fixed and the image saved on disk is transferred to the central server. This is necessary because the X-ray plates used by a CR machine are erased by the scan — once scanned, the image must be captured or it is lost forever. (This program exists, and is written in C++. It was written before exceptions were available, and I doubt it has been updated. The process of doing so would make an interesting test case (see Reference 6).

Assume the network connection is encapsulated as an object (call it an endpoint). If the endpoint destructor is called, and the connection is still open, the destructor will attempt to close it. If some error occurs it seems reasonable, given the nature of this program, that an exception should be thrown to indicate a network problem to higher levels.

This would seem like a perfect case for having a destructor throw an exception, but maybe not. A closer look at this program reveals that under normal circumstances the network connection should always be closed before the endpoint is destroyed. If the connection is still open when the destructor is invoked, it probably means the endpoint is being destroyed as a result of some other exception. If this is the case, then the last thing we want to do is throw another exception. In fact, we may be trying to destroy the endpoint as a result of a stack unwind that resulted from an exception thrown by another network function call. We want this exception to propagate.

So back to the question of whether a destructor should handle all possible exceptions or allow them to propagate. Destructors being the type of functions they are, it may make sense to ignore a lot of errors in destructors (i.e. catch the exceptions and not rethrow them). Nevertheless, at some point in coding a class or library, we have to acknowledge that our users will probably have a better view of the big picture than we do. There has to be a limit on how much we try to handle at a given level. This is especially true for destructors. The person who uses our classes has to accept some responsibility for how they are used. This means that any program that has to be fault tolerant must be designed and coded with special care regarding the types of exceptions it has to guard against.

If our medical imaging program is written correctly, then it will not depend upon destructors for normal functionality. The remote file will always be closed before the file object is destroyed; the network connection will always be closed before the network object is destroyed, etc. Exceptions are possible — in fact the program may depend upon them — but their occurrence will be anticipated as part of normal design, and they will be handled outside of the destructors. We are left with something of a paradox: the more fault tolerant a program has to be, the less likely it is going to have to worry about exceptions from destructors.

As noted above, I object to constructs such as

on aesthetic grounds alone, but there is a practical aspect as well. Templates are but one example of a case where we have no idea what kinds of exceptions are possible from the operations being invoked on the objects used to instantiate the template. There are times when we have to assume that the user knows what she is doing and stay out of the way. This goes even for exceptions propagating from destructors.

Goal VIII. Do not get too paranoid. As a final point, I have noted that it is possible to get paranoid when trying to deal with exceptions. Maybe this is only a problem for me because I have spent so much time lately worrying about exceptions. Still, I am willing to bet that other people are also going to run into this.

When you start trying to deal with every possible exception that might occur, you can get into a situation where you either have to assume that an operation will work, or call terminate(). Consider what happens to our Stack if we try to make sure that an exception in push() always leaves the Stack in a good state. As we discussed under Guideline 3, if the assignment in the final step of push fails, and T::operator=() does not leave the element at v_[top_] in a good state, then our Stack is not in a good state. We might try to guarantee that the Stack is always left in a good state like this:

If the constructor in the catch clause throws an exception, then we have left our Stack object in an undefined state. After destroying the object at v_[top_] we can not even expect the destructor for Stack to work. We must assume that the constructor will be able to reinitialize the object at v_[top_].

If you find yourself writing code like this, take it out. As we discussed in the beginning, exception handling is something that must pervade the entire program. At some point you have to turn the problem over to the user. Ultimately, only the application developer is in a position to really decide how to deal with certain errors. More often than not, the application will have to seek help from a human operator. In the final analysis, often the best we can do in handling an exception is to make sure our software stays together, and then propagate the exception and let a higher level deal with it.

Conclusions

As shown in this article, exceptions raise many issues that either were not there before, or which had much simpler solutions. Many of these Goals and Guidelines can be illustrated with simple examples, but are not simple to apply in real practice.

The good thing about exceptions is that not every program needs to be truly fault tolerant — in fact, very few do. What is much more important is that a program be robust. A robust program is resistant to errors — it either works correctly, or it does not work at all; whereas a fault tolerant program must actually recover from errors. There are obviously different levels of "robustness." For years, I have liberally sprinkled my code with "assert" statements to make them more robust. Since all an assert statement usually does is print a message and abort the program, there are those who might question whether this actually qualifies as an improvement in robustness. Certainly, this is not acceptable behavior in a shipping application. For this reason, assert statements are usually used only for debugging and disabled in the final build of an application.

Before exceptions, the only alternative to a deliberate abort was to return error flags and check them religiously. Exceptions provide a third possibility. If an exception is not handled at some point in a program, it will propagate out of function main() and invoke terminate(). Thus, an un-handled exception has much the same effect as an assert statement. Conversely, the program can choose to handle the exception (if only to provide a more graceful exit). For this reason alone, programmers are going to insist upon throwing exceptions, especially in library code — it relieves them of the problem of deciding whether to abort the program, or not. As noted in the beginning, throwing exceptions is easy, coping with them is difficult.

The simplest way to start out coping with exceptions is to have a single try/catch block in the main() function to provide a meaningful error message and a graceful exit of the program (Goal V applied to the entire program). A program written this way may not be any more robust than a properly written C program, but that level of robustness will have been obtained with a lot less effort (virtually none), and a lot less code. Just think of all the if statements that will not have to be written.

From that point, developers can start to actually apply these guidelines to libraries and functions in order to bring them up to a point where they can be used in a fault tolerant program. We have to be careful not to rush into using catch clauses to handle exceptions or we run the risk of falling back into the problem where we think the program is running fine but in fact it has entered an erroneous state. Nevertheless, when used correctly, exceptions can definitely improve the reliability of our programs. As the examples in this paper have shown, this may take a lot of work, but developing reliable software usually does.

References

  1. °Tom Cargill, "Exception Handling: A False Sense of Security", °C++ Report, Vol. 6, No. 9, November-December 1994.
  2. Harald M. Müller, "10 Rules for Handling Exception Handling Successfully," °C++ Report, Vol. 8, No. 1, January 1996.
  3. °Jack Reeves, "Exceptions and Standard C++", °C++ Report, Vol. 8, No. 5, May 1996.
  4. °Jack Reeves, °"Ten Guidelines for Exception Specifications", °C++ Report, Vol. 8, No. 7, July 1996.
  5. °Scott Meyers, "How to Navigate the Treacherous Waters of C++ Exception Handling", °Microsoft Systems Journal, Vol. 10, No. 11, November 1995.
  6. °Jack Reeves, °"Migrating from C to C++", °C++ Report, Vol. 7, No. 7, July-August 1995.

Listing 1:
  template<class T>
  class Stack
  {
    size_t nelems_;
    size_t top_;
    T* v_;
  public:
    size_t count() const { return top_; }
    void push(T);
    T pop();

    Stack();
    ~Stack();
    Stack(const Stack&);
    Stack& operator=(const Stack&);
  };
Listing 2:
  template<class X>
  class auto_ptr {
    X* p_;
  public:
    explicit auto_ptr(X* p = null) throw() : p_(p) {}
    auto_ptr(auto_ptr<X>& ap) throw() : p_(ap.release()) {}
    ~auto_ptr() {delete p_;}
    void operator=(auto_ptr<X>& rhs);

    X& operator*() const throw() {return *p_;}
    X* operator->() const throw() {return p_;}
    X* get() const throw() {return p_;}
    X* release() throw() {return reset(null);}
    X* reset(X* p) throw() {X* tp = p_; p_ = p; return tp;}

   static void remove(X*& x) {X* tp = x; x = null; delete tp;}
  };

  template<class X>
  inline void auto_ptr<X>::operator=(auto_ptr<X>& rhs)
  {
    if (this != &rhs) {
      remove(p_);
      p_ = rhs.release();
    }
  }
Listing 3:
  template<class X>
  class auto_array_ptr {
    X* p_;
  public:
    auto_array_ptr(X* p = null) throw() : p_(p) {}
    auto_array_ptr(auto_array_ptr<X>& ap) throw() :
      p_(ap.release()) {}
    ~auto_array_ptr() {delete[ ]p_;}
    void operator=(auto_array_ptr<X>& rhs);

    X& operator*() throw() {return *p_;}
    X& operator[ ](int i) throw() {return p_[i];}
    X operator[ ](int i) const throw() {return p_[i];}
    X* get() const throw() {return p_;}
    X* release() throw() {return reset(null);}
    X* reset(X* p) throw() {X* tp = p_; p_ = p; return tp;}

    static void remove(X*& x) {X* tp=x; x=null; delete[ ]tp;}
  };