Errors Related to Exceptions

Partially Constructed Objects

When an exception prevents a constructor from running to completion, C++ will not call the destructor of the partially constructed object. Member objects, however, will still be properly destructed. This is not to say that exceptions are to be avoided in constructors. In fact, throwing an exception is usually the best way to indicate that a constructor has failed. The alternative is leaving around a broken object, which the caller must check for validity.

While you should often allow exceptions to propagate out of a constructor, it is important to remember that the destructor will never be called in such cases, so you are responsible for cleaning up the partially constructed object (e.g. by catching and then rethrowing the exception, using smart pointers, etc.).

Result: memory leak.

Exceptions Leaving a Destructor

If a function throws an exception, that exception may be caught by the caller, or the caller’s caller, etc. This flexibility is what makes error handling via exceptions into such a powerful tool. In order to propagate through your program, however, an exception needs to leave one scope after another ? almost as if one function after another were returning from a chain of nested of calls. This process is called stack unwinding .

As a propagating exception unwinds the stack, it encounters local objects. Just like in a return from a function, these local objects must be properly destroyed. This is not a problem unless an object’s destructor throws an exception. Because there is no general way to decide which exception to continue processing (the currently active one or the the new one thrown by the destructor), C++ simply calls the global terminate function. By default, terminate calls abort, which abruptly ends the program. In consequence, local objects are not properly destructed. While the operating system should reclaim the memory when your program terminates, any complex resource that requires your destructors to run (e.g., a database connection) will not be properly cleaned up.

Another consequence of an exception leaving a destructor is that the destructor itself does not finish its work. This could lead to memory leaks. Your destructor does not necessarily have to throw an exception itself for the problem to happen. It is far more common that something else called by the destructor throws the exception. In general, it is best if you make sure that exceptions never propagate out of your destructors under any circumstances (even if your compiler implements the Boolean uncaught_exception function, which can be used to test if an exception is already in progress).

Result: memory leak (destructor does not run to completion); local objects will not be properly destructed if another exception is active. Various resource leaks and state inconsistency are therefore possible.

Improper Throwing

When throwing exceptions, it is important to remember that the object being thrown is always copied. Hence, it is safe to throw local objects, but throwing dynamically allocated objects by value or by reference will cause them to be leaked. Copying is always based on the static type of the object, so if you have a base-type reference to an object of a derived class, and you decide to throw that reference, an object of the base type will be thrown. This is another variant of the object slicing problem covered earlier.

A more subtle slicing error occurs when rethrowing exceptions. If you want to rethrow the exact same object that you got in your catch clause, simply use throw; ? not something like throw arg;. The latter will construct a new copy of the object, which will slice off any derived parts of the original.

You also need to make sure that the copy constructor of the class that you are throwing will not cause dangling references. It is generally not recommended to throw exceptions by pointer; in these situations, only the pointer itself, rather than the actual object, is copied. Thus, throwing a pointer to a local object is never safe. On the other hand, throwing a non-local object by pointer raises the question of whether it needs to be deallocated or not, and what is responsible for the deallocation.

Result: object slicing, dangling references, and memory leaks are all possible.

Improper Catching

Improper catching of exceptions can also lead to object slicing. As you might have guessed, catching by value will slice. The order of the catch clauses matters, too; always list the catch for an exception of a derived class before the catch of its base class. The exception mechanism uses the first catch clause that works, so listing base classes up front will always shadow the derived classes.

Result: object slicing; memory-related errors and other problems are possible if a base-class catch shadows a derived-class catch, thus preventing the latter from taking actions specific to a derived-type exception.

The Way Forward

This article has focused on finding the right role for C++ amongst today’s other popular languages, and on understanding its most difficult aspect: memory management. The tables of common memory-related errors presented here can be used as a handy reference, to find and avoid such errors in your own code. Subsequent articles of the series will continue to discuss C++ memory management in greater detail.

The second article will be devoted to describing the nature of the C++ memory management mechanism, so that you can begin to apply it creatively in your designs. After that, the third article will present a series of specific techniques that you can use as building blocks in your programs.

C++ memory management is an enormously useful tool for creating elegant software. Having gained a clear awareness of its dangers, you are now ready to understand its benefits. Enabling you to do so is, ultimately, the purpose of this series of articles.

By George Belotsky Initially published on Linux DevCenter (http://www.linuxdevcenter.com/)