- Failure to Distinguish Scalar and Array Allocation
- Checking for Allocation Failure
- Replacing Global New and Delete
- Confusing Scope and Activation of Member new and delete
- Throwing String Literals
- Improper Exception Mechanics
- Abusing Local Addresses
- Failure to Employ Resource Acquisition Is Initialization
- Improper Use of auto_ptr
Gotcha #68: Improper Use of auto_ptr
The standard auto_ptr template is a simple and useful resource handle with unusual copy semantics (see Gotcha #10). Most uses of auto_ptr are straightforward:
template <typename T> void print( Container<T> &c ) { auto_ptr< Iter<T> > i( c.genIter() ); for( i->reset(); !i->done(); i->next() ) { cout << i->get() << endl; examine( c ); } // implicit cleanup . . . }
This is a common use of auto_ptr to ensure that the storage and resources of a heap-allocated object are freed when the pointer that refers to it goes out of scope. (See Gotcha #90 for a more complete rendering of the Container hierarchy.) The assumption above is that the memory for the Iter<T> returned by genIter has been allocated from the heap. The auto_ptr< Iter<T> > will therefore invoke the delete operator to reclaim the object when the auto_ptr goes out of scope.
However, there are two common errors in the use of auto_ptr. The first is the assumption that an auto_ptr can refer to an array.
void calc( double src[], int len ) { double *tmp = new double[len]; // . . . delete [] tmp; }
The calc function is fragile, in that the allocated tmp array will not be recovered in the event that an exception occurs during execution of the function or if improper maintenance causes an early exit from the function. A resource handle is what's required, and auto_ptr is our standard resource handle:
void calc( double src[], int len ) { auto_ptr<double> tmp( new double[len] ); // . . . }
However, an auto_ptr is a standard resource handle to a single object, not to an array of objects. When tmp goes out of scope and its destructor is activated, a scalar deletion will be performed on the array of doubles that was allocated with an array new (see Gotcha #60), because, unfortunately, the compiler can't tell the difference between a pointer to an array and a pointer to a single object. Even more unfortunately, this code may occasionally work on some platforms, and the problem may be detected only when porting to a new platform or when upgrading to a new version of an existing platform.
A better solution is to use a standard vector to hold the array of doubles. A standard vector is essentially a resource handle for an array, a kind of "auto_array," but with many additional facilities. At the same time, it's probably a good idea to get rid of the primitive and dangerous use of a pointer formal argument masquerading as an array:
void calc( vector<double> &src ) { vector<double> tmp( src.size() ); // . . . }
The other common error is to use an auto_ptr as the element type of an STL container. STL containers don't make many demands on their elements, but they do require conventional copy semantics.
In fact, the standard defines auto_ptr in such a way that it's illegal to instantiate an STL container with an auto_ptr element type; such usage should produce a compile-time error (and probably a cryptic one, at that). However, many current implementations lag behind the standard.
In one common outdated implementation of auto_ptr, its copy semantics are actually suitable for use as the element type of a container, and they can be used successfully. That is, until you get a different or newer version of the standard library, at which time your code will fail to compile. Very annoying, but usually a straightforward fix.
A worse situation occurs when the implementation of auto_ptr is not fully standard, so that it's possible to use it to instantiate an STL container, but the copy semantics are not what is required by the STL. As described in Gotcha #10, copying an auto_ptr transfers control of the pointed-to object and sets the source of the copy to null:
auto_ptr<Employee> e1( new Hourly ); auto_ptr<Employee> e2( e1 ); // e1 is null e1 = e2; // e2 is null
This property is quite useful in many contexts but isn't what is required of an STL container element:
vector< auto_ptr<Employee> > payroll; // . . . list< auto_ptr<Employee> > temp; copy( payroll.begin(), payroll.end(), back_inserter(temp) );
On some platforms this code may compile and run, but it probably won't do what it should. The vector of Employee pointers will be copied into the list, but after the copy is complete, the vector will contain all null pointers!
Avoid the use of auto_ptr as an STL container element, even if your current platform allows you to get away with it.