Exceptions & Exception Specifications

Exceptions & Exception Specifications

By The Harpist

Overload, 6(28):, October 1998


I have just read the comments from Detlef Vollmann above. He raises a number of points but starts by making reference to 'dogma'. One person's dogma is someone else's idiom. In my mind, dogmas are beliefs that are held without reason. So let me try to present some reasons for using exceptions and exception specifications in C++.

Because I think it is important I am going to start by assuming that the reader knows very little of the background to exceptions.

Exceptions, What For

When you write a program you become aware of the things that may go wrong during its execution. For example, your program wants to open and read a file, but the operating system reports that there is no such file. How should you handle this?

It rather depends on the context. You may want to allow the user to try again but you may want to close the program down. If such a problem occurs fairly near the start of the program, closing down may be no big deal and code such as:

ifstream myfile("filename");
if (!myfile)
{
  cout << "File not found" << endl;
        return 1;
}

in main() may be appropriate. However:

ifstream myfile("filename");
if (!myfile) 
{
  cout << "File not found" << endl;
  exit(1);
}

in a member function would almost certainly be an extremely bad implementation decision. Basically it means that the failure to open a file will cause any program using that function to terminate. I will cover the use of exit(1) a little later.

Most of the time, a failure of this kind (cannot open a file) is unexpected (not normal behaviour) and will need some special handling. C provided three mechanisms: handle the problem locally, return to a previously provided location (via longjmp() to a location provided by a call to setjmp() ) or call exit . Handling the situation locally includes returning some form of error code which can, in theory, be used to provide alternative behaviour in the calling function.

The last of these mechanisms is often appropriate even in C++ but is unavailable in some situations (such as constructors and destructors). The other two mechanisms are inappropriate to C++ because they will not provide a proper cleanup of the stack. Actually, even in C, you need to be careful that you do not longjmp in a context that will loose access to some dynamically assigned resource. Essential cleanup before program termination can be provided by registering appropriate functions with atexit() . Actually, you could provide (if you are willing to code the required tools) something similar to handle cleanups required by calling longjmp but I have never seen anyone do so.

One of the big themes in C++ is the concept of reuse. This means that, among other things, code written by one programmer may be used by others in a wide variety of contexts. Frequently you will be able to detect a problem in your code but will need to pass it to the user for solution. A failure in a constructor should not result in immediate program closure. Before examining how exceptions can manage this problem, let me cover a couple of other ideas.

class CanFail 
{
  bool failed;
  // other private members
public:
  CanFail(): failed(true)
  {
    // body
    failed = true;
  }
  bool built()
  { return !failed; }
  // other public members
};

In other words the class contains a flag that the constructor sets to failure until the constructor is about to terminate. That way, after an attempt to construct an object returns you will be able to check whether it succeeded. A slightly different version of this technique is in wide use, even in the C++ Standard Library (I think). The built() function is replaced by:

operator void const * ()
{
  return (failed ? 0 : this);
}

Do not try to declare an operator bool() as it will not always work the way you expect. The significance of using operator void const *() is that there are no available implicit conversions from void * and the const qualification means that you can do virtually nothing with it except examine the returned address value. The zero return will behave as 'false' and any other value will behave as 'true'. The use of this is just a simple idiomatic way of providing a non-null address that must be in your program's actual address space (that is important because a program must not reference addresses outside that space).

A mechanism like this one is used to provide the check for successful opening of fstream objects. It works as long as the resulting object is stable enough for destruction. But it does depend on the programmer meticulously checking after every instance of such an object being constructed. However consider:

class CanFailEarly 
{
  Object1 one;
  Object2 two;
public:
  CanFailEarly();
}

CanFailEarly must construct two data members. If either fails, what should it do? Well there is no problem if the two objects are completely independent but suppose that two depends on one . If one has failed, how will you construct two ? Does that matter? Yes, because until you manage to construct two your instance of CanFailEarly will not be stable and cannot be safely destroyed.

In other words, we need some way of aborting construction that will, preferably, cleanup any part-built object and then notify the program that the object it tried to construct was never completed. There is no other acceptable general solution to failed construction other than throwing an exception. The process of handling an exception has been carefully honed over time so that a modern C++ compiler should cleanup all the constructed bits before exiting to your exception handler. If you do not provide such a handler then your program will unroll until it finds one. If it cannot find one it will be as if you aborted within the constructor. The first reaction to that last statement is shock. A little reflection will show that this is no worse than the best you could do without exceptions. Certainly any attempt to continue your program without managing the problem of an incompletely constructed object will be very dangerous.

Many constructors (if well written) cannot fail. Or to put it slightly differently, failure of most well written constructors could only be the result of some event so bizarre that extreme emergency action is needed. However your program has no way to recognise this circumstance unless you use exception specifications. When I write:

class CannotFail
{
  …
public:
  CannotFail() throw();
  …
};

I am not only telling the user that I believe I have written a robust constructor but I am also telling the compiler to prepare extreme emergency action should an exception propagate out of that constructor at run time. The provision of std::unexpected() (defaults to std::terminate() ) allows the programmer to provide that emergency action. The default means that unless you provide your own emergency action the program will terminate. That seems reasonable to me. In affect, when I add an empty exception specification I am asserting that this constructor will always succeed. Unlike the C assert() I have the ability to redefine what will result from an assertion failure.

If my constructors do not have throw specifications, what shall the user do? S/he knows that potentially any exception can propagate from this constructor but has no idea what ones. In other words I must prepare to handle anything. I think that is an unreasonable design. I should expect you to tell me in what ways your constructor may fail. It seems to me that your constructor might fail either because a standard exception has been raised or because a user supplied one has been. I know I can catch all standard exceptions with catch (exception & e) . If users meticulously use exception specifications I will know what others may occur.

The reason that the Standard Library usually omits an exception specification is because there are many instances where library code may call user code (check out the various handlers such as std::new_handler() ) where user provided exceptions might be thrown to be caught by a handler 'on the other side' of the library and so must pass through the library functions).

In general, it would be good practice to base all your exception types on a very limited set of base classes. At worst I think I should be able to write:

class MyClass 
{
  …
public:
  void func()
    throw(std::exception, 
      MyClassException);
  …
};  

Derived classes should base their exception hierarchies on the base class base exception.

If I do a correct job users will always know what they are expected to handle and I will handle everything else. At the moment this is difficult because so many programmers refuse to provide the necessary information. Every time I have to call a function without an exception specification I have to handle everything. For example (assuming a suitable exception hierarchy for MyClassException ):

void MyClass::func()
   throw (exception, MyClassException)
{
  try {
    // body of fucntion
  }
  catch (exception & e)
  { throw; }
  catch(…) 
  { throw MyClassException::unknown; }
}

That is the best I can do if any function called in the body lacks an exception specification.

I think that exception specifications are, to C++ programmers, what const is to C programmers. C++ programmers (good ones at least) know that using const and ensuring const correctness provides better code with fewer unexpected results. The next generation of C++ programmers may realise that exception specification correctness is a vital element to writing good robust code.

Overly generalised code is unhelpful. Learning to write and use exception specifications takes time but many of the perceived problems go away when you look beyond novice attempts. The problem with exception specifications in over-riders for virtual functions largely goes away once you start providing base class exception hierarchies. If you do not like cluttering up your class definitions then place the base for your class hierarchy in the namespace you are building your class hierarchy in. If you do not automatically encapsulate your class hierarchies in namespaces it is time to ask why not.

Exception Specifications and Destructors

Detlef seems to have a problem with destructors with empty exception specifiers. I have a problem with non-empty ones. Let me explain why.

You cannot have a container of any type whose destructor can throw.

That may seem a strong statement but I cannot see any way round. Consider when the container is being destroyed. Each element must itself be destroyed. This in turn may result in calling destructors for all the subobjects (base class objects or member objects) of each element. If any exception propagates out of the destructor of an element what happens next?

You will have a part destroyed container. You will not be able to roll back to the state before you started destruction and you will not be able to complete the process. Any exception propagating out of an element of a container destabilises the container. I know of no way round this. In extremis, almost every other action that you might take on a container can be provided with commit or rollback semantics (though you might think the performance cost exorbitant) but destruction cannot work that way. Once Humpty Dumpty has his great fall all the programmers in the World cannot put him back together again.

Note that I did not say the destructors must always have empty exception specifications. What I did say was that if you elected not to have an empty exception specification you should be required to document your decision.

Conclusion

Now that we have compilers that at least tolerate exception specifications we should learn to use them. This does not mean slapping an exception specifier on every function. It means that we must understand what they are for and how we should use them. It is my contention that by the time you get to the public interface for an application level class all functions should have exception specifications. In general every effort should be made to make these empty. The application level programmer should have to do very little catching of exceptions. To a large extent main should look like this:

int main()
{
  try
  {
    // your code
  }
  catch (ProgramEnd & pe)
  {
    pe.report();
    return pe.errorlevel();
  }
  catch (…)
  {
  cout
    << "Unexpected Program Termination" 
    << endl;
    return (1);
  };
  return 0;
}

This means that functions called in the try block can propagate a ProgramEnd exception to cause orderly termination of the program with a full cleanup of the stack. Just to be safe and ensure a cleanup if possible, we catch all other exceptions, print an error message and terminate with an error return to exit() . Finally the program can terminate normally through a normal return to main() . with a return 0 to exit() .

Of course it is difficult to achieve this level of clean programming while we have to handle so much dirty code but that is no reason for avoiding exceptions and exception specifiers.

Unlike Detlef, I think the problem lies with those who want to live without exceptions. Exceptions and exception specifiers are certainly useful for constructors (remember that if the constructor of a subobject throws an exception any already constructed subobjects will be destroyed). And it is close to essential for destructors to handle exceptions internally. I know we tend to hate new things, particularly when they cause us to change the habits of a lifetime.

To my way of thinking, a designer who fails to provide exception specifications is failing in his/her duty to users of the code. If you cannot promise me that your code will not propagate exceptions I have a right to ask you what exceptions it might propagate. Of course this is extra work, but writing reusable code is extra work, you do not get the benefits at zero cost. The earlier exceptions are specified the less the burden on users of the code. You may not like the constraint that exception specifications in a base class place on your derived class, but hey, you want to use my work and I should have the right to place constraints on how you use it. It's my ball so you play by my rules. In practical terms, I am (possibly legally) responsible for my code and should be able to limit the ways in which you can abuse it.

Good programmers qualify globals as static (or better, place them in an anonymous namespace) until they are certain that external linkage is necessary, they qualify member functions as const until they know they must mutate the object state, they qualify constructors as explicit until they are certain that implicit use is safe, they qualify functions with throw() until they know different. In each case we have to make explicit something that should have been the default behaviour but we live with our pasts. If we could start from scratch we might do many things differently including making no exception propagated the norm for functions.

An Aside

By the way, with all of its much heralded garbage collection, can someone tell me what guarantees Java provides with its exceptions. If the construction of an object that handles some resource other than memory fails do I know that the resource is released? Perhaps someone could write an article about exceptions, constructors and destruction in Java.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.