Cleaning up

Part of the magic of exception handling is that you can pop from normal program flow into the appropriate exception handler. Doing so wouldn?t be useful, however, if things weren?t cleaned up properly as the exception was thrown. C++ exception handling guarantees that as you leave a scope, all objects in that scope whose constructors have been completed will have destructors called.
Here?s an example that demonstrates that constructors that aren?t completed don?t have the associated destructors called. It also shows what happens when an exception is thrown in the middle of the creation of an array of objects:

The class Trace keeps track of objects so that you can trace program progress. It keeps a count of the number of objects created with a static data member counter and tracks the number of the particular object with objid
The main program creates a single object, n1 (objid 0), and then attempts to create an array of five Trace objects, but an exception is thrown before the third object is fully created. The object n2 is never created. You can see the results in the output of the program:

Three array elements are successfully created, but in the middle of the constructor for the fourth element, an exception is thrown. Because the fourth construction in main( ) (for array[3]) never completes, only the destructors for objects 1 and 2 are called. Finally, object n1 is destroyed, but not object n2, because it was never created.

Resource management

When writing code with exceptions, it?s particularly important that you always ask, ?If an exception occurs, will my resources be properly cleaned up?? Most of the time you?re fairly safe, but in constructors there?s a particular problem: if an exception is thrown before a constructor is completed, the associated destructor will not be called for that object. Thus, you must be especially diligent while writing your constructor.
The general difficulty is allocating resources in constructors. If an exception occurs in the constructor, the destructor doesn?t get a chance to deallocate the resource. This problem occurs most often with ?naked? pointers. For example:

The UseResources constructor is entered, and the Cat constructor is successfully completed for the three array objects. However, inside Dog::operator new( ), an exception is thrown (to simulate an out-of-memory error). Suddenly, you end up inside the handler, without the UseResources destructor being called. This is correct because the UseResources constructor was unable to finish, but it also means the Cat objects that were successfully created on the heap were never destroyed.

Making everything an object

To prevent such resource leaks, you must guard against these ?raw? resource allocations in one of two ways:
? You can catch exceptions inside the constructor and then release the resource.
? You can place the allocations inside an object?s constructor, and you can place the deallocations inside an object?s destructor.
Using the latter approach, each allocation becomes atomic, by virtue of being part of the lifetime of a local object, and if it fails, the other resource allocation objects are properly cleaned up during stack unwinding. This technique is called Resource Acquisition Is Initialization (RAII for short), because it equates resource control with object lifetime. Using templates is an excellent way to modify the previous example to achieve this:

The difference is the use of the template to wrap the pointers and make them into objects. The constructors for these objects are called before the body of the UseResources constructor, and any of these constructors that complete before an exception is thrown will have their associated destructors called during stack unwinding.

The PWrap template shows a more typical use of exceptions than you?ve seen so far: A nested class called RangeError is created to use in operator[ ] if its argument is out of range. Because operator[ ] returns a reference, it cannot return zero. (There are no null references.) This is a true exceptional condition?you don?t know what to do in the current context, and you can?t return an improbable value. In this example, RangeError is simple and assumes all the necessary information is in the class name, but you might also want to add a member that contains the value of the index, if that is useful.
Now the output is:

Again, the storage allocation for Dog throws an exception, but this time the array of Cat objects is properly cleaned up, so there is no memory leak.

auto_ptr

Since dynamic memory is the most frequent resource used in a typical C++ program, the standard provides an RAII wrapper for pointers to heap memory that automatically frees the memory. The auto_ptr class template, defined in the <memory> header, has a constructor that takes a pointer to its generic type (whatever you use in your code). The auto_ptr class template also overloads the pointer operators * and -> to forward these operations to the original pointer the auto_ptr object is holding. You can, therefore, use the auto_ptr object as if it were a raw pointer. Here?s how it works:

The TraceHeap class overloads the operator new and operator delete so you can see exactly what?s happening. Notice that, like any other class template, you specify the type you?re going to use in a template parameter. You don?t say TraceHeap*, however; auto_ptr already knows that it will be storing a pointer to your type. The second line of main( ) verifies that auto_ptr?s operator->( ) function applies the indirection to the original, underlying pointer. Most important, even though we didn?t explicitly delete the original pointer (in fact we can?t here, since we didn?t save its address in a variable anywhere), pMyObject?s destructor deletes the original pointer during stack unwinding, as the following output verifies:

Allocating TraceHeap object on the heap at address 8930040
5
Deleting TraceHeap object at address 8930040

The auto_ptr class template is also handy for pointer data members. Since class objects contained by value are always destructed, auto_ptr members always delete the raw pointer they wrap when the containing object is destructed.

Function-level try blocks

Since constructors can routinely throw exceptions, you might want to handle exceptions that occur when an object?s member or base subobjects are initialized. To do this, you can place the initialization of such subobjects in a function-level try block. In a departure from the usual syntax, the try block for constructor initializers is the constructor body, and the associated catch block follows the body of the constructor, as in the following example.

Notice that the initializer list in the constructor for Derived goes after the try keyword but before the constructor body. If an exception does indeed occur, the contained object is not constructed, so it makes no sense to return to the code that created it. For this reason, the only sensible thing to do is to throw an exception in the function-level catch clause.

In this case, the catch block can return in the same manner that the function body normally returns. Using this type of function-level try block isn?t much different from inserting a try-catch around the code inside of the function body.

Share
Tweet
Share
Pin