Most people have heard of the standard auto_ptr smart pointer facility, but not everyone uses it daily. That's a shame, because it turns out that auto_ptr neatly solves common C++ design and coding problems, and using it well can lead to more robust code. This article shows how to use auto_ptr correctly to make your code safer--and how to avoid the dangerous but common abuses of auto_ptr that create intermittent and hard-to-diagnose bugs.
Why Call It an "Auto" Pointer?
auto_ptr is just one of a wide array of possible smart pointers. Many commercial libraries provide more sophisticated kinds of smart pointers that can do wild and wonderful things, from managing reference counts to providing advanced proxy services. Think of the Standard C++ auto_ptr as the Ford Escort of smart pointers: A simple general-purpose smart pointer that doesn't have all the gizmos and luxuries of special-purpose or high-performance smart pointers, but that does many common things well and is perfectly suitable for regular daily use.
What auto_ptr does is own a dynamically allocated object and perform automatic cleanup when the object is no longer needed. Here's a simple example of code that's unsafe without auto_ptr:
// Example 1(a): Original code
//
void f()
{
T* pt( new T );
delete pt;
}
Most of us write code like this every day. If f() is a three-line function that doesn't do anything exceptional, this may be fine. But if f() never executes the delete statement, either because of an early return or because of an exception thrown during execution of the function body, then the allocated object is not deleted and we have a classic memory leak.
A simple way to make Example 1(a) safe is to wrap the pointer in a "smarter" pointer-like object that owns the pointer and that, when destroyed, deletes the pointed-at object automatically. Because this smart pointer is simply used as an automatic object (that is, one that's destroyed automatically when it goes out of scope), it's reasonably called an "auto" pointer:
// Example 1(b): Safe code, with auto_ptr
//
void f()
{
auto_ptr pt( new T );
} // cool: pt's destructor is called as it goes out
// of scope, and the object is deleted automatically
Now the code will not leak the T object, no matter whether the function exits normally or by means of an exception, because pt's destructor will always be called during stack unwinding. The cleanup happens automatically.
Finally, using an auto_ptr is just about as easy as using a built-in pointer, and to "take back" the resource and assume manual ownership again, we just call release():
// Example 2: Using an auto_ptr
//
void g()
{
T* pt1 = new T;
// right now, we own the allocated object
// pass ownership to an auto_ptr
auto_ptr pt2( pt1 );
// use the auto_ptr the same way
// we'd use a simple pointer
*pt2 = 12; // same as "*pt1 = 12;"
pt2->SomeFunc(); // same as "pt1->SomeFunc();"
// use get() to see the pointer value
assert( pt1 == pt2.get() );
// use release() to take back ownership
T* pt3 = pt2.release();
// delete the object ourselves, since now
// no auto_ptr owns it any more
delete pt3;
} // pt2 doesn't own any pointer, and so won't
// try to delete it... OK, no double delete
Finally, we can use auto_ptr's reset() function to reset the auto_ptr to own a different object. If the auto_ptr already owned an object, though, it first deletes the already-owned object, so calling reset() is much the same as destroying the auto_ptr and creating a new one that owns the new object:
// Example 3: Using reset()
//
void h()
{
auto_ptr pt( new T(1) );
pt.reset( new T(2) );
// deletes the first T that was
// allocated with "new T(1)"
} // finally, pt goes out of scope and
// the second T is also deleted
Wrapping Pointer Data Members
Similarly, auto_ptr can be used to safely wrap pointer data members. Consider the following common example that uses the Pimpl (or, compiler-firewall) Idiom:[1]
// Example 4(a): A typical Pimpl
//
// file c.h
//
class C
{
public:
C();
~C();
private:
class CImpl; // forward declaration
CImpl* pimpl_;
};
// file c.cpp
//
class C::CImpl { };
C::C() : pimpl_( new CImpl ) { }
C::~C() { delete pimpl_; }
In brief, C's private details are split off into a separate implementation object that's hidden behind an opaque pointer. The idea is that C's constructor is responsible for allocating the private helper "Pimpl" object that contains the class's hidden internals, and C's destructor is responsible for deallocating it. Using auto_ptr, however, we find an easier way:
// Example 4(b): A safer Pimpl, using auto_ptr
//
// file c.h
//
class C
{
public:
C();
private:
class CImpl; // forward declaration
auto_ptr pimpl_;
};
// file c.cpp
//