Using C++ Exceptions Effectively

Michael Smit


Table of Contents

License
Overview
Intro to C++ Exceptions
Why do we Need Exceptions?
What is an Exception
How Exceptions Work
Catching an Exception You've Never Met
So why is This an Improvement
Problems With C++ Exceptions
Dangling Resources
Inconsistent State
Exceptions in Constructors
Exceptions in Destructors
Exception Specifications
Testing Exception Handling Code
Conclusions and Guidelines
Throwing Exceptions
Handling Exceptions
Exception Specifications

License

This document is licensed under a Creative Commons license.

Overview

Many C++ coders ignore exceptions completely. Unfortunately, weather you know it or not you are programming with them all the time. Even the new operator can throw an exception and the STL library uses them extensively.

This article is intended to discuss the basic exception mechanism as well as some best practices to help you avoid common pitfalls. If you are already familiar with C++ exceptions please feel free to skip the intro and go right to the problems. If you are really in a hurry you can skip right down to the conclusions.

Intro to C++ Exceptions

Why do we Need Exceptions?

Exceptions are a replacement for the old C error codes. For instance, pthread_create returns an integer value indicating success status. If this integer is something other than zero the developer must check to see which error code was returned and take corrective action.

	int errorCode;
	
	errorCode = pthread_create(...);
	if(errorCode != 0)
	{
		switch(errorCode)
		{
		case EAGAIN:
			...
		}
	}

Why is this so bad? Well there are a number of reasons. First of all often you'd like to return something other than a number. In the case of fopen we get back a pointer. In this case the only way to indicate an error is to return NULL. NULL isn't very specific as errors go so now you also need errno to find out what the error actually was.Finally if the calling function then wants to pass this error status back to its calling function, you have to translate that error code into another error code and start all over.

What is an Exception

So what is the alternative? Well obviously exceptions. Exceptions are a way of transmitting error information that bypasses return values and parameters. In exception terminology the method which has the error throws an exception and the code calling that method can then try to catch the exception.

Catching an Exception

For instance, although you may be used to the new operator always working, it can actually fail. As it turns out when this happens an exception is thrown which you can catch.

try{ //Indicates that we are going to 
	//try to catch exceptions in the following code
	while(true)
	{
		int *i = new int[1000000];
	}
}catch(std::bad_alloc & allocationException)
{
	//do something here to recover or exit gracefully
}

Some operations may throw different exceptions each of which can then be caught.

try{
	someFunction();
}
catch(Exception &e)
{
	//something
}
catch(OtherException &oe)
{
  //something
}

Throwing an Exception

You can also throw your own exceptions. An exception can be anything up to and including a basic data type such as an int.

void SomeClass::someMethod(char *name)
{
	if(name == NULL)
	{
		throw NullArgumentExceptionClass();
	};
};

Re-Throwing an Exception

You can also re-throw a caught exception in the event that you don't know what to do with it:

void someFunction()
{
	try
	{
		//code here
	}
	catch(Exception &e)
	{
		//do some cleanup
		throw; //rethrow the exception
	}
};

When you re-throw an exception it is passed along to the next caller up in the same way as it was passed in the first place.

How Exceptions Work

So what exactly happens when an exception is thrown? Well first of all, the currently executing method or function exits immediately. All destructors are called as they would be on a return, but the return value of the method/function is not set.

If the calling method/function contains a try block with a catch that matches the type of the thrown exception then execution will jump directly to the catch block. Catch expressions are evaluated in the order declared in the same manner as function arguments. In other words, If class B extends class A then if B is thrown a catch for &A will match that exception.

void function()
{
	throw B();
};

int main()
{
	try{
		function();
	}
	catch(A &exception)
	{
		//this works
	}
}

Trying to catch with a non-reference A parameter also behaves as you would expect.

void function()
{
	throw B();
};

int main()
{
	try{
		function();
	}
	catch(A exception)
	{
		//this works, but the B object gets sliced down to A.
	}
}

If there is no catch block in the calling function or method it will also terminate immediately until the top-level function (usually main) is reached. If no catch statement is found the program will terminate.


void function1()
{
	throw Exception1();
};

int function2()
{
	int ret = 0;
	for(int i=0; i < 3; i++)
	{
		ret++; //will only get executed once
		function1();
	}
	return ret; //will never get executed
};

int main()
{
	int a = 12;
	try
	{
		a = function2();
		cout << "hello" << endl; //never executed
	}
	catch(Exception1 &exception)
	{
		cout << "Exception thrown" << endl; //executed.
	}
};

Catching an Exception You've Never Met

Since exceptions can be anything at all it is entirely possible you won't know what type to put in your catch block in all cases. Fortunately C++ has provided a way of catching these pesky exceptions:

try
{
	//some code here
}
catch(Exception &e)
{
  //some code
}
catch(...) //catch any exception not yet caught
{
  //some code
}

So why is This an Improvement

Well first of all, an exception doesn't require you to try to overload the meaning of a return value to include error status. Second, exceptions cannot be ignored. If you ignore an exception the program will terminate. Finally, an exception can contain a lot more information that an error code. An error code can only give you a category of problem; An exception can be an object that gives you details.

Problems With C++ Exceptions

Dangling Resources

The biggest problem with exceptions is that until they are caught they can leave a trail of dangling resources. Although destructors are called when a function/method is terminated by an exception, dynamic resources such as pointers are left dangling.

Inconsistent State

This is pretty similar to the dangling resources problem. Objects that don't properly plan for exceptions can be left in a state that then makes them unusable.

  1. Object never deallocates some resources.

  2. Object behaves in undefined ways after exception.

  3. Object is impossible to destroy after exception.

The worst of these problems is the last one because it makes it impossible for the application to recover from the error, but all three are a problem.

Exceptions in Constructors

When a constructor throws an uncaught exception, the compiler cannot simply call the object's destructor since the object has not been fully constructed. As a result the compiler tries to clean up for you. Theoretically, all fully constructed sub-elements in the class have their destructor called and any memory allocated for elements that have not been fully constructed is freed.

Example 1. Throwing an Exception in a Constructor

This example should produce the following output:

Unexceptional constructor
Exceptional constructor
Unexceptional destructor
Exception construction init

class Exceptional
{
public:
        Exceptional();
        ~Exceptional();
};

Exceptional::Exceptional()
{
        cout << "Exceptional constructor" << endl;
        throw 1;
};

Exceptional::~Exceptional() //Never Called!
{
        cout << "Exceptional destructor" << endl;
};

class Unexceptional
{
public:
        Unexceptional();
        ~Unexceptional();
};

Unexceptional::Unexceptional()
{
        cout << "Unexceptional constructor" << endl;
}

Unexceptional::~Unexceptional()
{
        cout << "Unexceptional destructor" << endl;
};

class InitTest
{
public:
        InitTest();
private:
        Unexceptional unexceptional;
        Exceptional exceptional;
};

InitTest::InitTest()
{
        cout << "Init test constructor" << endl;
}

int main()
{
        try{
                InitTest init;
        }
        catch(int i)
        {
                cout << "Exception construction init" << endl;
        };
};

Catching Exceptions in Initializer Lists

The C++ standard committee didn't like the idea that there could be some exceptions you couldn't catch in the enclosing method. The previous example would seem to be one. Since the constructor for Exceptional is called automatically when building an InitTest object it would seem there is no good way of catching the exception.

As a result the C++ committee came up with this:

A::A()
try
:B(...), C(...)
{
	//constructor code
}
catch(...)
{
	//exception code
};

The idea is that any exception thrown in the initializer list is caught by the catch block. The behavior is similar to that for a normal catch block except the exception is re-thrown even if you handle it. As a result, A is still not considered fully constructed.

Unfortunately, what happens next differs by compiler. If we use an initializer try block to catch Exceptional's exception:

InitTest::InitTest()
try
: exceptional()
{
        cout << "Init test constructor" << endl;
}
catch(int i)
{
        cout << "Exception in initializer list" << endl;
};

Under gcc you get this output (Exceptional is not destroyed):

Unexceptional constructor
Exceptional constructor
Unexceptional destructor
Exception in initializer list
Exception construction init
Under Visual Studio .NET you get this output (Exceptional is now destroyed):
Unexceptional constructor
Exceptional constructor
Unexceptional destructor
Exception in initializer list
Exceptional desctructor
Exception construction init

TBD. What does the standard say?

Exception in Constructor With New

The compiler should clean up any memory allocated by the new operator if the constructor for the object being created throws an exception. In other words I should not have to destroy ptr in the following:

int main()
{
	Exceptional *ptr;
	
	try{
		ptr = new Exceptional();
	}
	catch(int i)
	{
	}
};

Exception in Constructor of a Static Object

What happens when you declare a static variable and the constructor throws and exception? Well you can't catch it so as with other uncaught exceptions you get a program crash.

Exceptions in Destructors

You may be wondering what happens when an exception is thrown from a destructor. Any sub-elements in the object being destroyed automatically have their destructors called and the memory for the object is deallocated. Obviously it is still possible to have dangling pointers if you are not careful.

The problem arises when an exception is thrown which causes a destructor to run which in turn throws its own exception. In this case the program terminates.

The following example should produce this output:

Unexceptional constructor
Unexceptional constructor
ExceptionalDesctruct constructor
ExceptionalDestruct destructor
Unexceptional destructor
Unexceptional destructor
Caught exception

class Unexceptional
{
public:
        Unexceptional();
        ~Unexceptional();
};

Unexceptional::Unexceptional()
{
        cout << "Unexceptional constructor" << endl;
}

Unexceptional::~Unexceptional()
{
        cout << "Unexceptional destructor" << endl;
}

class ExceptionalDestruct
{
public:
        ExceptionalDestruct();
        ~ExceptionalDestruct();
private:
        Unexceptional unexceptional;
};

ExceptionalDestruct::ExceptionalDestruct()
{
        cout << "ExceptionalDesctruct constructor" << endl;
}

ExceptionalDestruct::~ExceptionalDestruct()
{
        cout << "ExceptionalDestruct destructor" << endl;
        throw 1;
};

void myFunction()
{
        Unexceptional unexceptional;
        ExceptionalDestruct exceptional;
};


int main()
{
        try
        {
                myFunction();
        }
        catch(int i)
        {
                cout << "Caught exception" << endl;
        }
};

Exception in Destructor of a Static Object

What happens when you declare a static variable and the destructor throws and exception? As with static constructor exceptions the application will crash.

Exception Specifications

One oft-ignored feature of the C++ language is exception specifications. I'll spill the beans up front here: don't use them. Ever. If you are curious as to why feel free to continue.

What are They?

One of the problems with exceptions is that there is no way to know which exceptions are thrown by which methods. In the case of spotty documentation it can be difficult to figure out what to put in your catch block. In general you pretty much have to assume anything at all could throw anything.

Exception specifications attempt to solve this problem by allowing you to declare what exceptions a method throws as part of the method declaration.

class MyClass{
public:
	void myMethod() throw(Exception1, Exception2);
	void myMethod2() throw(); //This can't throw anything
};

On the face of it this looks like the best thing since sliced bread and a nice analog of Java exception specifications. Unfortunately there are some problems.

Problems

First of all, Microsoft Visual Studio .NET doesn't support any exception declarations other than throw(). They compile but are ignored which results in exception specifications becoming glorified comments.

Even worse, since these specifications have to be re-declared in the header and source file for this an every extending class, it is highly likely that as you update them they will end up as incorrect glorified comments.

Now if you have gcc you may be inclined to laugh at Microsoft and go on your merry way. Calm down and try this example:

class MyClass
{
public:
        void myMethod() throw();
        void myOtherMethod() throw(Exception);
};

void MyClass::myMethod() throw()
{
        myOtherMethod(); //clearly throws Exception
};

void MyClass::myOtherMethod() throw(Exception)
{
        throw Exception();
};

This blatant violation of the contract doesn't warrant a peep from the compiler. So what's going on? This is the fundamental problem with C++ exception specifications: they are only checked at runtime. When an undeclared exception is thrown the default behavior is to immediately terminate the program.

It is possible to override this behavior by defining std::unexpected, but since this is one global function for the entire application (possibly containing libraries you did not write) basically all this does is let you pull the trigger yourself.

So to summarize, the best case scenario before was that you would use (...) to catch unknown exceptions and attempt to go on your merry way; with exception specifications the best case scenario is that you terminate your program completely. In fact this sounds a lot like the old worst case scenario before exception specifications.

Testing Exception Handling Code

This is an issue which programmers in other languages talk about a lot. C++ programmers less so. The reason of course is that it is often very difficult to force an exception to happen given that the exception happens in only exceptional circumstances. For instance, how do you test your code for handling a failed new call?

Conclusions and Guidelines

Throwing Exceptions

When Should I Throw an Exception?

  1. Exceptions should only be thrown in exceptional circumstances to indicate an error.They should never be used as a glorified goto statement for passing results through a call hierarchy.

  2. Exceptions should never be thrown from a destructor. There is very little a developer can do with a destructor exception and they can easily cause the program to abort when a cascading destructor sequence throws multiple exceptions. Also, destructor exceptions are very difficult to catch when the object has been statically declared.

What Should I Throw as an Exception?

  1. Always throw an object. Non-object exceptions are almost as bad as return codes and usually result in a lot of (...) catch statements.

  2. All thrown objects should come from the same object hierarchy.If all exceptions in your library extend from MyException then the developer can always get some sort of type information about any exception you throw.

  3. The exception hierarchy should use virtual methods. I hope this goes without saying.

  4. That hierarchy is ideally std::exception. std::exception is already defined for you and all the STL exceptions derive from it so why not use it?

When Should I Re-throw and Exception?

For the most part there are two main reasons to re-throw exceptions

  1. You would like to add some annotation to the exception that is not available in the throwing method.

  2. You need to do clean resources in the event of an exception, but cannot handle it.

Handling Exceptions

What is an Exception-Safe Object?

When designing exception safe code there are three ways an object can fail:

  1. Object goes into an undefined state. May be indestructible.

  2. Object goes into an error state, but can be destroyed.

  3. Object stays in a defined state and can continue being used.

Basically you want to aim for the third and failing that go for the second. Most code you run across falls into category one which makes using exceptions difficult.

When Should I Catch an Exception?

  1. Whenever you allocate resources that will result in a dangling reference if an exception is thrown, that code should be enclosed in a try block. This should be rare if you use the strategies suggested in (TBD)

  2. Whenever an exception might be thrown that would be impossible to handle gracefully in the calling method. The exception should either be caught and handled or caught and replaced by a new, more appropriate exception. Obviously C++ doesn't make this easy since anything can theoretically throw an exception of any type.

  3. main or thread functions should always catch all exceptions. It is generally good practice to catch and at least report any exceptions thrown from within main or the top level function for a thread. In either case the execution thread simply aborts if the exception is uncaught leaving the user to wonder what happened.

Using Destructors to Write Exception Safe Code

Try/catch blocks can really clutter up a piece of code. This is particularly true in C++ where any function,method, or operator can throw an exception. Fortunately C++ provides a mechanism for writing exception-safe code in the form of destructors.

Resource Managers and std::auto_ptr<T>

Resource managers are static objects that encapsulate a dynamic resource. The destructor of the resource manager cleans up that resource. Using a resource manager in a method means that if an uncaught exception is thrown dynamic resources are cleaned up automatically.

The C++ STL actually provides a resource manager for pointers called std::auto_ptr. auto_ptr uses overloaded operator* and operator-> methods to mimic a pointer's behavior so that is can be used very much in the same way.

Widget *some_function()
{
	auto_ptr<Widget> widget;
	
	widget->some_method();
	...
	//widget.release() releases control of the dynamic resource
	return widget.release();
}
	
Smart pointers and Reference Counting

As resource managers go auto_ptr is pretty brain dead. There are a whole class of resource managers called smart pointers some of which can count the number of outstanding references of a particular dynamic resource and destroy it only after the last reference is destroyed.

Catch by Reference or Value

Always catch by reference for the same reasons that methods taking virtual classes should use reference parameters.

Exception Specifications

When Should I use Them?

Never. At best they are useless and at worst they are actively misleading.