Some Useful Facts to Know Before Using C++ Exceptions

Exceptions provide many benefits over error codes for error handling. Some of these benefits are;

  • Exceptions cannot be silently ignored whereas checking error code of a method can be ignored by the method caller.
  • Exceptions propagate automatically over method boundaries, but error codes do not.
  • Exception handling removes error handling and recovery from the main line of control flow that makes code more readable and maintainable.
  • Exceptions are the best way to reporting errors from constructors and operators.

Despite these benefits most people still do not prefer to use exceptions due to its overhead. Depending on the implementation, this overhead comes in two forms: time overhead (increased execution time) and space overhead (increased executable size and memory consumption). From these two, most are concerned about time overhead. However, in a good C++ exception implementation, unless an exception is thrown, no run-time overhead is incurred [2]. The real issue about C++ exceptions is not in its execution performance, but it is about, how to use them correctly. Following are some useful facts to know to use C++ exception correctly.

1. Exceptions should not be thrown from destructors

Consider the following code;

try
{
MyClass1 Obj1;
Obj1.DosomeWork();


}
catch(std::exception & ex)
{
//do error handling
}

If an exception is thrown from the MyClass1:: DosomeWork() method, before execution move out from the try block, destructor of obj1 needs to be called as obj1 is a properly constructed object. What if an exception is also thrown from the destructor of MyClass1. This exception occurred while another exception is active. If an exception is thrown while another exception is active, C++ behavior is to call terminate() method, which halts the execution of the application. Therefore, to avoid two exceptions being active in the same time, destructors must not throw exceptions.

2. Objects thrown as exceptions are always copied.

When an exception is thrown, a copy of the exception object always needs to be created as original object goes out of the scope during the stack unwinding process. Therefore, the exception object’s copy constructor will always be called. C++ provides a copy constructor by default if we don’t provide one. But there may be cases where the default copy constructor doesn’t work; specially when the class members are pointers. If we use such objects as exception objects we should make sure that our exception classes have proper copy constructors. The most important thing of copying objects in C++ is that the copy constructor is always called based on the static type not the dynamic type. Consider the following code [1].

class Widget { … };
class SpecialWidget: public Widget { … };
void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;

Widget& rw = localSpecialWidget; // rw refers to a SpecialWidget
  throw rw; // this throws an exception of type Widget!
}

In the above code, throw statement throws an object of type Widget, rw’s static type is Widget. This might not be the behavior that we want to execute.

3. Exceptions should be caught from by reference

There are three ways to catch exceptions in the catch clause.

  1. Catch exception by value
  2. Catch exception as a pointer
  3. Catch exception by reference.

Catching exceptions by value is costly and it suffers from the slicing problem. It is costly because it needs to create two exception objects every time. When an exception is thrown, a copy of the exception object needs to be created no matter how we catch it, because when the stack unwinds the original object would go out the scope. If the exception was caught by value, another copy is made to pass it to the catch clause. Therefore, if the exception was caught by value two exception objects are created making the exception throwing process slower.

Slicing problem comes into effect when the catch clause is declared to catch super class type and a derived class object is thrown as an exception. In that case, catch clause only receives a copy of the super class object which lacks the attributes of the original exception object. Therefore, catching exceptions by value must be avoided.

If exceptions were caught as pointers, the code would be as follows;

void doSomething()
{
try
{
someFunction(); // might throw an exception*
}
catch (exception* ex)
{ // catches the exception*;
… // no object is copied
}
}

In order to catch the exception as a pointer, the pointer to the exception object should be thrown and throwing party must ensure that the exception object will be kept alive even after the stack unwinding as a result of the throw. Even though a copy of exception object will be created, it is a copy of the pointer in this case. Therefore other measures should be taken to keep the exception object alive after the throw. This can be achieved by passing the pointer to a global or a static object, or creating the exception object in the heap.

Nevertheless, user who catches the exception has no idea about how the exception object was created, he is uncertain about whether to delete the receiving pointer at the catch clause or not. Therefore, catching exception as a pointer is sub-optimal. Furthermore, all exceptions are thrown from standard functions are objects, not pointers.

Catching exceptions by reference doesn’t have any of these issues that are observable in catch as a pointer or catch by value. User doesn’t need to worry about the deletion of exception object. No additional exception object will be copied, as reference to the original exception object is passed. Furthermore, pass by reference doesn’t have the slicing problem. Therefore, exception should be caught by reference for proper and efficient operation.

4. Prevent resource leaks in case of exceptions

Consider the following code ;

void SomeFunction()
{
SimpleObject* pObj = new SimpleObject();
pObj->DoSomeWork();//could throw exceptions
delete pObj;
}

In this method new SimpleObject is created, then some work has been done through SimpleObject::DoSomeWork() method and finally the object is destroyed. But what if Object::DoSomeWork() threw an exception? In that case we don’t get a chance to delete the pObj. This introduces a memory leak. This is a simple example to illustrate that exceptions could lead to resource leaks and of course this can be eliminated by putting a simple try catch block. But in real life this scenario could happen from points of code where we can’t figure out the leak at first glance. One remedy for this type of cases is to use auto pointers from standard library (std::auto_ptr) [1].

5. When throwing an exception, do not leave the object in an inconsistent state

Consider the following example code [4];

template <class T>
class Stack
{
unsigned nelems;
int top;
T* v;
public:
void push(T);
T pop();
  Stack();
~Stack();
};
template <class T>
void Stack<T>::push(T element)
{
top++;
if( top == nelems-1 )
{
T* new_buffer = new (nothrow) T[nelems+=10];
if( new_buffer == 0 )
throw “out of memory”;
     for(int i = 0; i < top; i++)
new_buffer[i] = v[i];
     delete [ ] v;
v = new_buffer;
}
v[top] = element;
}

If exception “out of memory” was thrown, the Stack<T>::push() method leaves the Stack object in an inconsistent state, because the top of the stack has been incremented without pushing any element. Of course this code can be modified so that it won’t happen. However, special care needs to be taken when throwing an exception so that the object which throws the exception will be in a valid state even after the exception.

Furthermore, this scenario happens frequently with mutexes or locks. In the following naïve implementation of ThreadSafeQueue::Pushback() method , if an exception was thrown from DoPushBack() method the _mutex will be kept locked, leaving the ThreadSafeQueue object in an inconsistent state. To overcome this situation, lock_guards can be used, as auto pointers are used to prevent memory leaks. It should be noted that, lock_guard is only available in standard library since C++11, yet one can easily implement a lock_guard class.

template <class T>
void ThreadSafeQueue::Pushback(T element)
{
_mutex.Lock();
DoPushBack(T);
_mutex.Unlock();
}

6. Exception specification should be used carefully [1]

If a method throws an exception not listed in its exception specification, that fault is detected at runtime, and the special function unexpected() is automatically invoked. The default behavior for unexpected is to call terminate(), and the default behavior for terminate() is to call abort(). Therefore, the default behavior for a program with a violated exception specification is to halt. Consider the following code;

void f1(); // might throw anything
void f2() throw(int); //throws only int
void f2() throw(int)
{

f1(); // legal even though f1 might throw
// something besides an int

}

Above code is legal, because this kind of situations might arise when integrating old code that lacks exception specification, with new code. But if f1() throws something other than int the program will terminate because f2() is not allowed throw anything other than int. it is bit hard to stop arising this problem but there are many ways to handle this if arises. Reference 1:item14 details remedies for this problem.

7. Exceptions should be re-thrown with re-throw

There are two ways to propagate a caught exception to callers. Consider following two codes;

catch (Widget& w) // catch Widget exceptions
{
… // handle the exception
throw; // rethrow the exception so it continues to propagate
}

And;

catch (Widget& w) // catch Widget exceptions
{
… // handle the exception
throw w; // propagate a copy of the
}

The only difference between these two blocks is that the first one re-throws the current exception, while the second one throws a new copy of the current exception. There are two problems with the second case. One is the performance cost because of the copy operation. Other thing is the slicing problem. If the exception object is a child type of Widget, only the Widget part of the exception object is re-thrown as the copy is always based on the static type.

8. Catch clause for the base class should be placed after the catch clause for the derived class.

When an exception is thrown, catch clauses are matched in the order as they appear in the code. An exception object’s type also matches with its super types in catch clauses, because child type is also a subset of super type. Therefore, when a child object’s exception is thrown, if super type catch clause appear first in the code, that will be executed, even though the catch clause for the child type is there after the super type’s catch clause.

References:

[1] More Effective C++, by Scott Meyers, 1996

[2] When and How to Use Exceptions, By Herb Sutter, 2004 (http://www.drdobbs.com/when-and-how-to-use-exceptions/184401836)

[3] Technical Report on C++ Performance., 2005 (http://www.stroustrup.com/performanceTR.pdf)

[4] Exception Handling: A False Sense of Security, by Tom Cargill ( http://ptgmedia.pearsoncmg.com/images/020163371x/supplements/Exception_Handling_Article.html )