Using Exceptions Effectively: Part I
Coping With Exceptions

by

Jack W. Reeves
©C++ Report - 1996
(Revised 1998)

Introduction

The first version of this article was actually written in late 1995, and appeared in The C++ Report in March 1996. At the time it was written, the C++ Standardization committee had released the first Committee Draft of the Standard. This froze the feature set of the language and the libraries, but ultimately much work remained to be done on the details. The second Committee Draft of the C++ Standard was released to the public in December 1996. The Final Draft was approved in November 1997, though it was May 1998 before it was released by ISO to the public (two days ago actually).

In the first Committee Draft, Chapter 15 - Exception Handling was 7 pages long. By the second Committee Draft it had grown to 9 pages; still one of the shortest chapters in the entire Standard. As I noted in the first version of this article, if one were to judge the complexity of using exceptions solely on the number of sentences needed to specify their behavior in the language, it would be easy to conclude that exceptions were one of the easier aspects of C++ to master.

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. The original version of this article discussed the problems with using exceptions and presented 8 general guidelines (now increased to 10) and a number of specific tactics for coping with those problems. In the intervening two years, I feel that the original article has held up very well. What has changed however, is some of the terminology in use in the C++ community, and in the C++ Standard itself.

The original 1995 Committee Draft was completely silent on the concept of "exception safety" in the Standard Library. A number of members of the committee felt that this was unacceptable. Around the time of the second Committee Draft, a couple of different proposals were put forth to correct this situation. The Final Draft Standard therefore now has some general requirements on how certain Standard Library functions must cope with exceptions. While the basic principals in the Final Draft Standard correspond very closely with a couple of the guidelines in the original article, the Standard uses different terminology. Given that the arrival of Standard C++ finally seems imminent, it seemed reasonable to revise the original article to at least include a mention or two of what the Standard itself actually says.

Using exceptions effectively is difficult for at least 3 reasons:

i. In order to be effective, exception handling must pervade the entire application -- it can not be localized to a function or even a class.

ii. Exceptions are almost like asynchronous events -- they do not respect semi-colons and can appear from the middle of an expression.

iii. Exceptions can not be ignored or 'put off' -- if not handled

an exception will propagate up the call stack, eventually aborting the entire program.

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 [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:

while (*i++ = *j++) ;

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. For such types, both the post-increment operator (++), and the de-reference operator (*) represent user defined function calls. Assume that i iterates over a container of A objects (and *i returns an A&), and j iterates over a container of B objects (and *j returns a B&). 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 some user-defined conversion function. Another user-defined conversion function may have to be invoked to convert the result of the assignment statement (presumably an A&, but potentially anything) 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, or j or *i.

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 difficult. Nevertheless, I have also considered the alternatives. For example, 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:

  • • Set an internal error flag. An operator! function or a conversion to bool or void* is often provided to simplify testing the state of the object. Such a scheme can be used to tell if a constructor (or an operator) succeeded or failed, but it does not tell anything about why the failure occurred. For that, a member function that returns the value of an error code is usually provided.

    • Two-stage construction. In this scheme, the actual object constructor only initializes memory. A second "initialization" function is called after an object is 'constructed' to do resource allocation and any other complex operations that might encounter errors. This scheme does provide a means to return error indications to the user, but the user has to know about the need to initialize the object before it can be used. It does not work with overload operators.

  • 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 guidelines that I have put together on how to cope with exceptions (typically these days this topic is given the broad general title of "exception safety"). These guidelines are general, high level concepts. Complying with a guideline may involve re-writing a function or major changes to a class library. After each guideline, I present more specific tactics which might be used in certain circumstances to satisfy a guideline. As with most such offerings, every user will have to evaluate their own circumstances and determine which of these guidelines and tactics are applicable to their situation. I must admit that I have come across a couple of situations where issues about exception handling were approaching the status of religion. Under the circumstances, I therefore 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 remain interested in hearing from readers who have applied these and other guidelines on real projects -- successfully or not. The more collective knowledge we 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 a separate article [2], Dr. Harald Mueller presents a more general discussion of the problems I discuss in guidelines 1 through 3. In his article, Dr. Mueller 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 of the time (though I will occasionally use the term "resource leak"). In Dr. Mueller's article, he defines and discusses how resources can be in either "good" or "bad" states. For myself, I originally preferred a slightly different terminology than Dr. Mueller's. Then along came the Final Draft of the C++ Standard and introduced its own terminology. Not surprisingly, the Standard is only interested in specifying requirements, so while it may be the Standard, a slightly broader terminology is useful for a more general purpose discussion. Rather than try to provide solid definitions for such overloaded terms as "bad", I will try to use terms that have fairly obvious meanings to most C++ programmers.

    I will assume that we all know pretty much what is meant when we say that an object is in a good state. If we assume that we start with an object in a good state, and then we invoke a function that can change the state of the object, and that function exits with an exception, the question is "what state is the object left in?" Naturally, the actual state depends on the object, the function, and the exception, but for discussion purposes we group all these final states into several broad categories:

    undefined - when the Standard says that something is undefined, it means that the details of some particular behavior are outside the scope of the Standard. The more broadly understood meaning of "undefined" is that it "breaks" (the most cynical definition is that it works in test, but breaks at the customer's). In this context, when I say that an exception has left an object in an undefined state, I mean that all future operations on that object yield undefined behavior, including attempting to destroy the object.

    destructible - Dr. Mueller called this a "valid shutdown" state, and considered it a subset of the "bad" states. In the original article, I called this a "bad" state, but considered it better than being in an "undefined" state. In either case the point is the same: if the object is not in a "good" state, but the destructor of the object can be safely run and the object destroyed, then it is in a destructible state. As I will discuss below, this category may cover a lot of states, from just a "little inconsistent" where many operations might still work, to "really screwed up" where only the destructor will work. I decided to switch to the term "destructible" because that is what we are usually talking about anyway, and it makes more sense than overloading "bad", "inconsistent", or "invalid".

    consistent state - this is a term used in the Standard. It is the state an object is left in when a function provides what is called "the Basic Guarantee." I must admit that I find this terminology slightly confusing. I am sure that many people beside myself consider "consistent" to be synonymous with "good". The Standard makes it clear that you can not assume anything at all about the actual state of such an object. Nevertheless, there are often operations that can be applied to an object in a good state that are independent of what the current state may be -- assignment is a typical example. Furthermore, it is usually possible to make inquires about the specific state of a good object. On the other hand, I can easily show examples where it is possible for an exception to leave an object in an state where neither assignment nor typical inquiries make sense, yet the object would still be destructible. I would not consider such an object to be in a "consistent" state, yet the program that contained such an object could continue to run correctly. To my thinking, destructibility is thus the most basic requirement of an object after an exception. As a result, I have taken the position that what the Standard refers to as a "consistent state" is the same as what I call "destructible." I will elaborate upon my reasons for this below.

    initial state - in other words, it is as if the function was never attempted at all. Obviously, this is a good state for the object (by definition). Equally obvious, this is a very desirable state to be left in after an exception. The Standard requirement that a function leave the object in its initial state after an exception is referred to as "the Strong Guarantee" This point will be discussed much further in the guidelines below.

    For certain examples in the rest of this article, I use a Stack template class that was originally presented by Cargill in [1], and elaborated upon by Dr. Mueller in [2]. 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: (i)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: const long null = 0L; (ii)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.

    Table 1 summarizes the guidelines that are presented in the next section.

    Table 1. Guidelines for Coping with Exceptions

    1. When you propagate an exception, try to leave the object in the state it had when the function was entered.

    2. 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.

    3. If you can not leave the object in a "good" state, make sure the destructor will still work.

    4. Avoid resource leaks.

    5. Do not catch any exception you do not have to.

    6. Do not hide exception information from other parts of the program that might need it.

    7. Unless you have a Strong guarantee of exception safety, assume that you must destroy the object after any exception.

    8. Always catch an exception by reference.

    9. Never depend upon destructors for functionality in any situation where fault-tolerance is required.

    10. Do not get too paranoid.

     

    Catching and Propagationg 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 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 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, objects in inconsistent 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?

     

    Guideline 1. - 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. The C++ Standard refers to this as "The Strong Guarantee of Exception Safety". In other words, when a function provides "the Strong Guarantee", it is assuring its clients that the function will leave the object unchanged if an exception occurs.

    I personally feel that writing code that meets this goal is very difficult. In an extreme case, meeting this goal might involve making a complete copy of the state of an object, attempting the operation on the copy, and then either reverting to the original if the operation fails, or removing it and going forward with the now changed copy as the object's new state. Nevertheless, I put this guideline 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 [2]. I will only give a few simple suggestions here.

    Tactic 1a. 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. As we all know however, const can be subverted, either by designating some members mutable, or with a const_cast. 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 change the physical 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 corrupt my object under some circumstances. If you discover such a function in your class, I might urge reconsidering whether it should be a const function. An example of this actually occurs in the Standard C++ Library. The basic_string function c_str() is a const function, yet it appears that an implementation is allowed to reallocate the internal character array if necessary to make room for the terminating NULL character. This could throw a bad_alloc exception. In this case const is taken to mean "logical const-ness". When a const function exhibits such behavior, it has numerous ramifications, most of them bad.

    Tactic 1b. Perform exception prone operations early, and/or through temporaries.

    Consider the Stack::push function:

    template<class T>

    Stack<T>::push(T element)

    {

    if (top_ == nelems) {

    T* new_buffer = new T[nelems*= 2]; //>x1

    for (int i = 0; i < top; i++)

    new_buffer[i] = v[i]; //>x2

    delete[] v; //>x3

    v = new_buffer;

    }

    v[top++] = element; //>x4

    }

    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 no longer in a good 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 (this is required by the Standard).

    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 Guideline 4 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 have left the object in an inconsistent 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_deleted_array_ptr 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_deleted_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_deleted_array_ptr is a variation of the auto_ptr template class defined in the utilities section of the C++ Standard Library. I discuss auto_ptr and my own variations more fully under guideline 4 below. For now, the key point is that an auto_deleted_array_ptr is an object which owns the array pointer. The destructor of auto_deleted_array_ptr calls delete[] on the pointer it owns. The header for auto_deleted_array_ptr is shown in listing 3. Using an auto_deleted_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:

    template<class T>

    Stack<T>::push(const T& element)

    {

    if (top == nelems) {

    size_t new_nelems = nelems * 2;

    auto_deleted_array_ptr<T> new_buffer =

    new T[new_nelems]; //>x1

    for (int i = 0; i < top; i++)

    new_buffer[i] = v[i]; //>x2

    v = new_buffer.reset(v); //3

    nelems = new_nelems;

    }

    v[top] = element; //>x4

    top++;

    }

    The statement at line 3 swaps ownership of the old buffer and the new buffer. After the statement, the auto_deleted_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) can help in this task, but they are not a complete answer. In particular, template programmers do not have access to the exception specifications of classes used to instantiate the template. 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_deleted_array_ptr::reset() is safe because all it does is swap two built-in pointer types through a temporary.

    The auto_deleted_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 tactic c.

    Tactic 1c. 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::opertor=() was guaranteed to leave us in an inconsistent state (at least) because top would have been incremented before T::operator=() was called. Since T::operator=() might throw an exception, we can avoid the side effect to top by postponing the increment.

    Originally, I thought it made more sense to postpone the side effect as I show above. In practice this has not turned out to be such good advice. The problem is that the statements at x4 do not look right to any experienced C/C++ programmer. This means that they are forever in danger of being changed by someone doing maintenance. Considering that exceptions are suppose to be rare events, I have concluded that it makes more sense to make the exception safety explicit by actually using a try/catch block in this case. These days, I would write the last two statements of the example above as:

    try {

    v[top++] = element;

    } catch (...) {

    --top; // reset state on exception

    }

    Note that even though this makes sure top has the correct value, 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 only in a destructible state, then we must consider the Stack object to also only be in a destructible state -- we can not expect another call to push() to work if the stack buffer now contains an inconsistent object. 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 Guideline 1, then the stack object does also.

    For a slightly more complicated example consider the Stack::pop() function. In its original form:

    template<class T>

    T Stack<T>::pop()

    {

    if (top == 0)

    throw "pop on empty stack";

    return v[--top]; //>x1

    }

    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):

    if (top == 0)

    throw domain_error("empty stack");

    try {

    return v[--top];

    } catch (...) {

    ++top;

    throw;

    }

    The domain_error class is one of the Standard library exception classes. It is derived from logic_error, which is in turn derived from exception. As discussed in the second article of this series, 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.

    Guideline 2. 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 loose the old state of the object, we would like to be able to reuse the object. I have come very close to taking this guideline out of the article on several occasions. The problem is that if you do not know what the actual state of the object is after an exception, then there is very little that you can reasonably do with the object, other than destroy it. Nevertheless, I have left the guideline in the article, and in its No. 2 position, simply because it makes sense as a goal of good class design. In particular, there are often operations that make sense without knowing the exact state of an object. A good example of this is the assignment operator. Typically with assignment we expect to loose the old value of the object when we assign a new value to it anyway. Consider the Stack assignment operator (in its initial form):

    template<class T>

    Stack<T>& operator=(const Stack<T>& s)

    {

    if (&s == this) return *this;

    delete[] v; //>x1

    v = new T[nelems = s.nelems]; //>x2

    for (top = 0; top < s.top; top++)

    v[top] = s.v[top]; //>x3

    return *this;

    }

    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 Guideline 1, but for now observe that if an exception propagates from line x2 we are in an undefined state (not even 'destructible'). 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 (Guideline 3 -- 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 Guideline 1, but let us assume that we could not do that.

    Tactic 2a. Either reinitialize the object, or mark it internally to indicate that it is no longer usable but might be recovered.

    Another reason why we want to leave objects in a good state, if possible, is that our clients might not be paying as much attention to exception handling as they should. Even though Guideline 6 (below) emphasizes that the only safe thing to do after an exception where Guideline 1 is not guaranteed is to destroy the object, our clients might handle the exception, but not realize that the object is no longer valid. In such cases, we want to make sure that any further attempt to use the object will be rejected.

    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 becomes aware of the problem. Rewriting Stack::operator=() to become

    template<class T>

    Stack<T>& operator=(const Stack<T>& s)

    {

    if (&s == this) return *this;

    auto_deleted_array_ptr::remove(v); //>x1

    top = 0; nelems = 0; // reinitialize object

    v = new T[s.nelems]; //>x2

    for (size_t i = 0; i < s.top; i++)

    v[i] = s.v[i]; //>x3

    nelems = s.nelems;

    top = s.top;

    return *this;

    }

    Line x1 deletes the array through a temporary (auto_deleted_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 Guideline 1, but this is a good start.

    Guideline 3. - 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 (Guideline 1 or 2) it may not always be possible (or at least not practical). As a last resort, we want to leave the object in such a state that it can always 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.

    Tactic 3a. 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_deleted_ptr (and auto_deleted_array_ptr) class to facilitate this operation. Calling auto_deleted_ptr::remove(p) (auto_deleted_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 type objects as members of your class instead of raw data pointers, the task becomes even easier. If ap is an auto_ptr object, then:

    delete ap.release();

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

     

    Guideline 4. - 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 inconsistent 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.

    It is not possible to discuss exceptions and memory leaks without some discussion of the auto_ptr<> template class provided by the Standard C++ Library. Auto_ptr<> is declared in header <utility>. It is a template which takes a single template argument of class type. Conceptually, auto_ptr<> is a very simple class that has two functions. First, and foremost, it holds a pointer to an object of its argument type. As the name suggests, auto_ptr<> objects are intended to be automatic objects. These are the objects for which the compiler will automatically invoke the destructors. Such objects include objects which are members of other objects. When an auto_ptr<> object is destroyed, it calls delete on the pointer it holds.

    The second function that the auto_ptr<> class provides is the ability to transfer the ownership of a pointer from one auto_ptr<> object to another. This is conceptually very useful when you want to allocate an object in one function but some other function must be responsible for deleting the object later. Before auto_ptr<>, this "transfer of responsibility" had to be handled with documentation and trust. Now you can make explicit in the code the fact that ownership is being transferred. Unfortunately, these two tasks of auto_ptr<> are somewhat at odds with each other, and auto_ptr<> ends up depending upon a very subtle use of member templates in order to correctly implement the functionality described here. Most users will not need to know or care how auto_ptr<> is implemented, however; they will simply use them and everything will work.

    There are a couple of things that will not work with though, and they are deliberate. The first and most obvious of these is that auto_ptr<> objects do not meet the requirements for use in Standard containers, most especially in vector<>. For this reason, auto_ptr<> is designed so that it will generate compile errors if you try to instantiate a container with an auto_ptr<> type. This often surprises people, but there are good reasons for it (which we will not go into here), so just accept it. The other thing auto_ptr<> was not designed to handle is a pointer to an array. Auto_ptr<>'s destructor calls delete, not delete[]. As you know, calling delete on a pointer that actually points to an array of objects results in undefined behavior. Unfortunately, this usage will go undetected by the compiler.

    In the examples below, I show some uses of the standard auto_ptr<> class. I also show some uses of my own version(s) of auto_ptr<>. While most users will want to stick with the Standard auto_ptr<> class, but I decided to do my own version for a couple of reasons. My primary reason was that none of my compilers actually provide a "standard" version of auto_ptr<> yet, so I was going to have to develop my own in any case. Furthermore, I wanted some functionality in my version of auto_ptr<> that I knew wasn't in the standard version, and I didn't need some of the standard auto_ptr<> functionality that is impossible to implement without member template support.

    My version of auto_ptr<> is called auto_deleted_ptr<> and is shown in listing 2. Like the standard auto_ptr<> it owns its pointer and deletes it when the auto_deleted_ptr<> object is destroyed. Unlike the real auto_ptr<>, you can not use my auto_deleted_ptr<> objects to transfer ownership of a pointer to another object. My auto_deleted_ptr<> also differs from auto_ptr<> in how its reset function works. My version returns the old pointer; the standard version has a void return and treats reset just like assignment. Finally, I created a second auto_ptr<> class to deal with pointers to arrays: auto_deleted_array_ptr<>. It is just like auto_deleted_ptr<> except that it calls delete[] instead of delete in its destructor. Now, what can we do with these auto_ptr<> classes?

    Broadly speaking, 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 sub-objects. If, during the construction, a resource (such as memory) is obtained directly and not as part of a sub-object whose destructor will release the resource, then a resource leak can occur. For example:

    class T1 { ... };

    class T2 { ... };

    class X {

    T1* p1;

    T2* p2;

    public:

    X();

    ~X();

    };

    X::X()

    {

    p1 = new T1;

    p2 = new T2; // exception causes leak

    }

    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.

    X::X() try :

    p1(null), p2(null)

    {

    p1 = new T1;

    p2 = new T2;

    }

    catch (...) {

    delete p2;

    delete p1; // reverse order

    throw; // redundant

    }

    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 ctor-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. All this leads to:

    Tactic 4a: 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. This is where auto_ptr<>s come in. In this case, we make sure that every resource is associated with an object whose destructor will deallocate it. Applied to our example:

    class X {

    auto_ptr<T1> ap1;

    auto_ptr<T2> ap2;

    public:

    X();

    ~X() {};

    };

    X::X() :

    ap1(new T1),

    ap2(new T2)

    {}

    Alternatively:

    X::X()

    {

    ap1.reset(new T1);

    ap2.reset(new T2);

    }

    Now, since ap1 and ap2 are both auto_ptr<> 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 a release function which allows you to transfer ownership of a resource from the auto_ptr<> object to the class. In this case, we can use an "acquire then transfer ownership" strategy to give us the following version of X's constructor:

    class X {

    T1* p1;

    T2* p2;

    public:

    X();

    ~X() {delete p1; delete p2;}

    };

    X::X() :

    p1(null), p2(null)

    {

    auto_ptr<T1> t1(new T1);

    auto_ptr<T2> t2(new T2);

    p1_ = t1.release();

    p2_ = t2.release();

    }

    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 Tactic 1b 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. 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 Tactic 1b we used an auto_deleted_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=():

    template<class T>

    Stack<T>& Stack::operator=(const Stack<T>& rhs)

    {

    if (this == &rhs) return *this;

    auto_deleted_array_ptr<T> new_buffer =

    new T[rhs.nelems]; //>x1

    for (int i = 0; i < rhs.top; i++)

    new_buffer[i] = rhs.v[i]; //>x2

    v = new_buffer.reset(v); // swap ownership

    nelems = rhs.nelems;

    top = rhs.top;

    return *this;

    }

    Here I have to use an auto_deleted_array_ptr<> instead of an auto_deleted_ptr<> (or a plain auto_ptr<>). Since I am using my own class, I take advantage of the fact that reset returns the current pointer to effect an ownership swap idiom. If we were dealing with ordinary objects instead of arrays, and if v were an auto_ptr<> object instead of a raw pointer, we would use the Standard template function swap() to perform an exchange of ownership as in:

    swap(v, new_object);

    Finally, we can have resource leaks in destructors. As a general rule, we do not want to throw (or propagate) exceptions from destructors. 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.

    X::~X() { delete p2; delete p1; }

    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 sub-objects that still exist are destroyed (in reverse order of their construction) and then the memory deallocation function is called, if needed. 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.

    X::~X()

    {

    auto_ptr<T1> t1(p1);

    auto_ptr<T2> t2(p2);

    }

    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.

     

    Guideline 5. - 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 computers) to catch an exception you do not know how to handle.

    Tactic 5a: 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 Guideline 5 is that we want to avoid this as much as we can. There may be mitigating circumstances, however. Consider our rewritten push() function. The last two lines could read:

    v[top] = element;

    ++top;

    This deliberately moves the incrementing of top after the possible exception from the assignment operator. We could write this like:

    try {

    v[top++] = element;

    } catch (...) {

    --top; // reset state

    throw;

    }

    This is similar to what we have to do in the pop() function. In that case we do not have any choice; in push() we do. Guideline 5 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 however, when we write a try/catch block, it is obvious that there is the possibility of an exception. If we write:

    v[top] = element;

    ++top;

    instead of

    v[top++] = element;

    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. This may turn out to be one of the most annoying drawbacks to using exceptions in C++, i.e. 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 this particular case, we may actually prefer to leave the statement in its original form and make our exception handling explicit

    try {

    v[top++] = element;

    } catch (...) {

    --top;

    throw;

    }

    We have to be very careful doing this, however. Remember that the assignment statement is actually a function call. Broken out, it looks something like this:

    v[top++].operator=(element)

    The language rules guarantee that all side effects (in this case top++) have taken place before the function is entered. In this case, we know that element is bound to a reference of the same type, which is an exception safe operation, so we know for certain that top will have been incremented if an exception occurs from within the assignment function. Now consider the more general case of

    v[top++] = x;

    where x is not the same type as T, but is a type which can be converted to a T. Now the function breakdown looks like

    v[top++].operator=(T(x));

    Now, besides the possibility of an exception from T::operator=(), we might also have the possibility of an exception from the T(x) constructor (or from X::operator T(), whichever is actually used to do the conversion). The language rules allow the compiler to perform the top++ operation before or after it creates the temporary T object. Now if we try to write

    try {

    v[top++] = x;

    } catch (...) {

    --top;

    }

    we have no guarantee that top actually was incremented before the exception occured.

    Generally, I still think you are better off rearranging code, but that means in order to avoid the 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.

    Tactic 5b. - Always use a catch(...) block to cope with propagating exceptions.

    If we follow Tactic 5a 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 an inconsistent 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:

    catch (...) { /* ... */ }

    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.

    Tactic 5c. - Do not "handle" any exception that can not be "fixed."

    This tactic falls between those for just coping with propagating exceptions and those for handling exceptions. I put it here since it is a special case of the more general Guideline. The idea is to avoid getting yourself into an infinite loop where you "handle" an exception without fixing the problem (usually by just ignoring it), and then retry the operation again. This is especially a problem when your code depends upon user intervention to actually "fix" the problem. There are few things in life more frustrating than computers that tell you to do something that you either can not or do not want to do, but refuse to let you out of the mode they are in unless you comply with their instructions. If you put error messages up to the user, be sure your dialog box allows them to cancel the attempted operation as well as retry it. If you do retrys internally, make sure you put a limit on the number of retrys you attempt before giving up and propagating the exception to a higher level.

    Tactic 5d. - 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.

     

    Guideline 6. 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 the possibility of the exception being handled by doing so.

    Tactic 6a. - 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;

    Tactic 6b. - Rethrow a different exception only to increase the level of abstraction 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, but meaningless, exception will probably be second. Consider the following:

    void foo() try

    {

    // do something

    }

    catch (PrivateExceptionType& ex) {

    // clean up

    }

    catch (...) {

    // clean up

    throw exception("Unknown exception in foo");

    }

    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 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 as in:

    new_buffer = new (nothrow()) T[new_nelems];

    if (new_buffer == null)

    throw Stack<T>::AllocationError(new_nelems);

    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 classic 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:

    try {

    new_buffer = new T[new_nelems];

    } catch (...) { // catch bad_alloc from new

    throw Stack<T>::AllocationError(new_nelems);

    }

    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.

    Tactic 6c. - 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. 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.

     

    Guideline 7. Unless you have a Strong Guarantee of exception safety (Guideline 1), assume you must destroy the object after any exception.

    Now we start getting into guidelines (and tactics) for actually handling exceptions. Ideally, when you handle an exception, you correct whatever was wrong and then redo the failed operation. In order for this to work, you have to be sure that the operation(s) which failed left their object(s) in the state they had before the operation(s) was attempted. This is Guideline 1, and is what the C++ Standard calls the "Strong Guarantee". If you can not say for certain that Guideline 1 is being followed, then all bets are off. If Guideline 2 has been followed, you might be in a position to reuse the object, but in general the only really safe thing to do is destroy the object and start over.

    Consider our stack<> example. Suppose we have a function that reads some input from a user, parses that input, and does something with the result. Suppose further that we use a stack<> to hold the parsed result. We might start out with something like this

    void do_something() {

    string in_str;

    stack<X> results;

    // prompt the user

    cin >> in_str;

    parse_it(in_str, &results);

    // do something with the results

    }

    Now consider exceptions from parse. Suppose we decide that we will "handle" the possible exceptions by issuing an error message and having the user retry. We might start out with just a minimal try/catch block inside a loop

    for (;;) try {

    cin >> in_str;

    parse(in_str, &results);

    } catch (...) {

    cout << "Error, please try again\n";

    }

    There are several things wrong with this approach. Obviously, we need to catch only the exception(s) we know about (Guideline 6), but I use the catch-all form for simplicity. Also, this is a good example of the point in Tactic 5c where there is no way out of the loop if an exception occurs. Finally, I do not necessarily recommend this coding style. I have adopted it on occasion since I find that it clearly indicates that the for loop is intimately connected with the try/catch. The real point in this case is that we are attempting to re-use both the stack<> and the string objects after we have an exception. Since parse() has a prototype of

    void parse(const string&, stack<X>*)

    we are probably safe in re-using the string object (see Tactic 1a). The stack<> is another story. If parse() guarantees to follow Guideline 1 (The Strong Guarantee), then everything is fine. Of course, for parse() to be able to make this guarantee, it depends upon everything that it uses, including all string and stack<> functions, also providing the Strong Guarantee. Since we are assuming that not all of our stack<> functions provide this guarantee, we can not assume that parse() does either. So we come to the following tactic.

    Tactic 7a. -- Put everything that needs to be destroyed inside the try block.

    A better attempt at this function is

    for (;;) try {

    string in_str;

    stack<X> results;

    cin >> in_str;

    parse(in_str, &results);

    // do something with results

    break;

    } catch (...) {

    cout << "Error, please try again\n";

    }

    By putting the declarations of in_str and results inside the try/catch block, we guarantee that they will be destroyed by the stack unwind BEFORE the catch clause is entered. We might want to keep in_str outside of the loop if we want to have the user's input still available inside the catch clause.

    Tactic 7b. Explicitly destroy and re-construct objects which might be damaged by exceptions.

    There might occasionally be times when you can not afford to have everything inside the try block. Suppose that our do_something function already contained a loop getting input from the user. Because of the overhead of destroying one stack and then allocating a new one on each iteration of the loop, we might prefer our function to have the following form

    string in_str;

    stack<X> results;

    for (;;) try {

    cin >> in_str;

    if (in_str.empty()) break;

    parse(in_str, &results);

    // do something with the results

    } catch (...) {

    cout << "Error: please retry\n";

    (&results)->stack<X>::~stack<X>;

    new(&results) stack<X>;

    }

    This uses the explicit destructor call mechanism to destroy the object in place, and then uses the placement form of operator new to reconstruct the object. Obviously this is really ugly compared to just letting the compiler handle it, so I consider this a last resort approach. Nevertheless, keep it in mind. It beats having your program blow up because you tried to reuse an object that was in an inconsistent state.

    Guideline 8. Always catch an exception by reference.

    I have included this guideline mostly as a reminder. It was not in the original article primarily because I just assumed that everyone already knew this. That is probably a valid assumption, but it doesn't hurt to be reminded. When a catch clause is entered, the exception mechanism will initialize the parameter of the catch clause from the actual exception in much the same way as the parameters to a function are initialized from the real arguments. If your parameter type is a real object, then the copy constructor will be invoked to do the initialization. Besides the extra overhead, this also means that if your catch clause specifies a base type of the actual exception type, the copy constructor will 'slice' the actual exception to just the base class component. To restate the obvious, if you have

    catch (exception ex) {

    cerr << ex.what();

    }

    and someone throws a domain_error (which is derived from exception), this catch clause will catch the domain_error, but when the clause is entered, the domain_error will have been sliced to just be an exception. This will cause the output to be whatever implementation specific string is generated by exception::what() instead of the string in the domain_error object. Catching by reference

    catch (exception& ex) {

    cerr << ex.what();

    }

    means that the call to what() is a virtual function call, and it yields the string used to create the domain_error object.

     

    Guideline 9. 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. 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[2,3]. I do not go quite that far, but the following tactics should apply under most circumstances.

    Tactic 9a. Do not throw exceptions from a destructor body.

    This is the principal behind Guideline 5 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()).

    Tactic 9b. 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 esthetic 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. 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

    catch (...) {}

    on esthetic 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.

    Guideline 10. - 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 Tactic 1c, 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:

    try {

    v[top] = element;

    ++top;

    } catch (...) {

    v[top].~T(); // destroy the 'bad' object

    new (&v[top]) T(); // create new 'good' object

    throw;

    }

    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 Guidelines and Tactics 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 (Guideline 5 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.

     

    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_deleted_ptr {

    X* _p;

    public:

    explicit auto_ptr(X* p = null) throw() : _p(p) {}

    auto_deleted_ptr(auto_deleted_ptr<X>& ap) throw() : _p(ap.release()) {}

    ~auto_deleted_ptr() {delete _p;}

    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;}

    private:

    void operator=(auto_deleted_ptr<X>&); // no assignment

    };

    Listing 3.

    template<class X>

    class auto_deleted_array_ptr {

    X* _p;

    public:

    auto_deleted_array_ptr(X* p = null) throw() : _p(p) {}

    auto_deleted_array_ptr(auto_deleted_array_ptr<X>& ap) throw() :

    _p(ap.release()) {}

    ~auto_deleted_array_ptr() {delete[] _p;}

    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;}

    private:

    void operator=(auto_deleted_array_ptr<X>&);

    };

     

    References

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

    2. Harald M. Mueller, "10 Rules for Handling Exception Handling Successfully," C++ Report, Vol. 8, No. 1, January 1996.

    3. Scott Meyers, "How to Navigate the Treacherous Waters of C++ Exception Handling", Microsoft Systems Journal, Vol. 10, No. 11, November 1995.

    4. Jack Reeves, "Migrating from C to C++", C++ Report, Vol. 7, No. 7, July-August 1995.