New, Constructors, and Exceptions



Item 41. New, Constructors, and Exceptions

To write perfectly exception safe code, it's necessary to keep track of any allocated resources and to be prepared to release them if an exception occurs. This is often a straightforward process. We can either organize our code in such a way that no resource recovery is necessary (see Exception Safe Functions [39, 135]) or use resource handles to recover the resources automatically (see RAII [40, 139]). In extreme situations, we can get down and dirty with try blocks or even nested try blocks, but this should be an exception, not the rule.

We do, however, have an apparent problem with the use of the new operator. The new operator actually performs two separate operations (see Placement New [35, 119]); first it calls a function named operator new to allocate some storage, and then it may invoke a constructor to turn that uninitialized storage into an object:

String *title = new String( "Kicks" );

The problem is that, if an exception occurs, we can't tell whether it was thrown by operator new or the String constructor. This matters, because if operator new succeeds and the constructor throws an exception, we should probably call operator delete on the allocated (but uninitialized) storage. If operator new was the function that threw the exception, no memory was allocated and we should not call operator delete.

One horrible approach is to handcraft the proper behavior by separating the allocation and initialization behavior and tossing in a try block:

String *title // allocate raw storage                                   
    = static_cast<String *>(::operator new(sizeof(String));             
try {                                                                   
    new( title ) String( "Kicks" ); // placement new                    
}                                                                       
catch( ... ) {                                                          
    ::operator delete( title ); // clean up if ctor throws              
}                                                                       

Ouch. So many things are wrong with this code that the approach is not worth considering. In addition to being more trouble for you, the overworked coder, it will not behave properly if String has a member operator new and operator delete (see Class-Specific Memory Management [36, 123]). This is a perfect example of too-clever code that works initially but fails subtly in the future because of a remote change (for example, if someone adds String-specific memory management).

Fortunately, the compiler handles this situation for us and produces code that performs in the same way as in our hand-coded approach above, but with one exception. It will invoke the operator delete that corresponds to the operator new used to perform the allocation.

String *title = new String( "Kicks" ); // use members if present
String *title = ::new String( "Kicks" ); // use global new/delete

In particular, if the allocation uses a member operator new, then the corresponding member operator delete will be called to reclaim the storage if the String constructor throws an exception. This is yet another good reason to declare a member operator delete if you declare a member operator new.

Essentially the same situation applies to array allocation and allocations that use overloaded versions of operator new[]; the compiler will attempt to find and call the appropriate operator delete[].