This article appeared in C/C++ Users Journal,
17(10), October 1999.
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 );
/*
more code
*/
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<T> pt( new T );
/*
more code
*/
} // 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<T> 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<T> 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<CImpl> pimpl_;
};
// file c.cpp
//
class C::CImpl { /*
*/ };
C::C() : pimpl_( new CImpl ) { }
Now the destructor
doesn't need to worry about deleting the pimpl_
pointer, because the auto_ptr
will handle it automatically. In fact, if there's no other reason for explicitly
writing a destructor, we don't need to bother with a custom destructor at all
any more. Clearly, this is easier than managing the pointer manually, and it
follows the good practice of wrapping resource ownership in objects--a job that
auto_ptr
is well suited to do. We'll revisit this example again at the end.
Ownership, Sources, and Sinks
This is nifty stuff
all by itself, but it gets better: It's also very useful to pass auto_ptrs
to and from functions, as function parameters and return values.
To see why, first
consider what happens when you copy an auto_ptr:
An auto_ptr
owns the object that it holds a pointer to, and only one auto_ptr
may own an object at a time. When you copy an auto_ptr,
you automatically transfer ownership from the source auto_ptr
to the target auto_ptr;
if the target auto_ptr
already owns an object, that object is first freed. After the copy, only the
target auto_ptr
owns the pointer and will delete it in due time, while the source is set back to
a null state and can no longer be used to refer to the owned object.
For example:
// Example 5: Transferring ownership from
// one auto_ptr to another
//
void f()
{
auto_ptr<T> pt1( new T );
auto_ptr<T> pt2;
pt1->DoSomething(); // OK
pt2 = pt1; // now pt2 owns the pointer,
// and pt1 does not
pt2->DoSomething(); // OK
} // as we go out of scope, pt2's destructor
// deletes the pointer, but pt1's does nothing
But be careful to
avoid the pitfall of trying to use a non-owning auto_ptr:
// Example 6: Never try to do work through
// a non-owning auto_ptr
//
void f()
{
auto_ptr<T> pt1( new T );
auto_ptr<T> pt2;
pt2 = pt1; // now pt2 owns the pointer, and
// pt1 does not
pt1->DoSomething();
// error! following a null pointer
}
With that in mind,
we start to see how well auto_ptr
works with sources and sinks. A "source" is a function or other operation that
creates a new resource, and then typically hands off and relinquishes ownership
of the resource. A "sink" is a function that does the reverse, namely that takes
ownership of an existing object (and typically disposes of it). Instead of just
having sources and sinks return and take bald pointers, though, it's usually
better to return or take a smart pointer that owns the resource:
// Example 7: Sources and sinks
//
// A creator function that builds a new
// resource and then hands off ownership.
//
auto_ptr<T> Source()
{
return auto_ptr<T>( new T );
}
// A disposal function that takes ownership
// of an existing resource and frees it.
//
void Sink( auto_ptr<T> pt )
{
}
// Sample code to exercise the above:
auto_ptr<T> pt( Source() ); // takes ownership
Note the elegance
of what's going on here:
1. Source()
allocates a new object and returns it to the caller in a completely safe way, by
letting the caller assume ownership of the pointer. Even if the caller ignores
the return value (of course, you would never write code that ignores return
values, right?), the allocated object will always be safely deleted.
At the end of this article, I'll demonstrate
why returning an auto_ptr
is an important idiom. It turns out that returning a result by wrapping it in
something like an auto_ptr
is sometimes the only way to make a function strongly exception-safe.
2. Sink()
takes an auto_ptr
by value and therefore assumes ownership of it. When Sink()
is done, the deletion is performed as the local auto_ptr
object goes out of scope (as long as Sink()
itself hasn't handed off ownership to someone else). The Sink()
function as written above doesn't actually do anything with its parameter, so
calling "Sink(
pt );" is a fancy way of writing "pt.reset(0);",
but normally a sink function would do some work with the object before freeing
it.
Things Not To Do, and Why Not To Do
Them
Beware: Never use
auto_ptrs
except in one of the ways I just described above. I have seen many programmers
try to use auto_ptrs
in other ways just as they would use any other object. The problem with this is
that auto_ptrs
are most assuredly not like any other
object. Here's the fundamental issue, and I'll highlight it to make sure it
stands out:
For auto_ptr, copies are NOT equivalent.
It turns out that
this has important effects when you try to use auto_ptrs
with generic code that does make copies and isn't necessarily aware that copies
aren't equivalent (after all, usually copies are!). Consider the following code that I regularly
see posted on the C++ newsgroups:
// Example 8: Danger, Will Robinson!
//
vector< auto_ptr<T> > v;
/*
*/
sort( v.begin(), v.end() );
It is never safe to put auto_ptrs
into standard containers. Some people will tell you that their compiler and
library compiles this fine, and others will tell you that they've seen exactly
this example recommended in the documentation of a certain popular compiler;
don't listen to them.
The problem is that
auto_ptr
does not quite meet the requirements of a type you can put into containers,
because copies of auto_ptrs
are not equivalent. For one thing, there's nothing that says a vector
can't just decide to up and make an "extra" internal copy of some object it
contains. For another, when you call generic functions that will copy elements,
like sort()
does, the functions have to be able to assume that copies are going to be
equivalent. At least one popular sort internally takes a copy of a "pivot"
element, and if you try to make it work on auto_ptrs
it will merrily take a copy of the pivot auto_ptr
object (thereby taking ownership and putting it in a temporary auto_ptr
on the side), do the rest of its work on the sequence (including taking further
copies of the now-non-owning auto_ptr
that was picked as a pivot value), and when the sort is over the pivot is
destroyed and you have a problem: At least one auto_ptr
in the sequence (the one that was the pivot value) no longer owns the pointer it
once held, and in fact the pointer it held has already been deleted!
So the standards
committee bent over backwards to do everything it could to help you out: The
Standard auto_ptr
was deliberately and specifically designed to break if you try to use it with
the standard containers (or, at least, to break with most natural
implementations of the standard library). To do this, the committee used a
trick: auto_ptr's
copy constructor and copy assignment operator take references to non-const
to the right-hand-side object. The standard containers' single-element insert()
functions take a reference to const,
and hence won't work with auto_ptrs.
Interlude: The const auto_ptr Idiom
One cute and
intentional result of this engineering of auto_ptr
is that const
auto_ptrs never lose ownership: Copying a const
auto_ptr is illegal, and in fact the only things you can do with a const
auto_ptr are dereference it with operator*()
or operator->()
or call get()
to inquire about the value of the contained pointer. This means that we have a
clear and concise idiom to express that an auto_ptr
can never lose ownership:
// Example 9: The const auto_ptr idiom
//
const auto_ptr<T> pt1( new T );
// making pt1 const guarantees that pt1 can
// never be copied to another auto_ptr, and
// so is guaranteed to never lose ownership
auto_ptr<T> pt2( pt1 ); // illegal
auto_ptr<T> pt3;
pt3 = pt1; // illegal
pt1.release(); // illegal
pt1.reset( new T ); // illegal
Now that's what I
call const!
So if you want to declare to the world that an auto_ptr
can never be changed and will always delete what it owns, this is the way to do
it. The const
auto_ptr idiom is a useful and common technique, and one that you should
keep in mind.
auto_ptr and Exception Safety
Finally, auto_ptr
is sometimes essential to writing exception-safe code. Consider the following
function:
// Example 10(a): Exception-safe?
//
String f()
{
String result;
result = "some value";
cout << "some output";
return result;
}
This function has two visible side effects: It emits some
output, and it returns a String. A detailed examination of exception safety is
beyond the scope of this article,[2] but the
goal we want to achieve is the strong exception-safety guarantee, which boils
down to ensuring that the function acts atomically--even if there are
exceptions, either all side effects happen or none of them do.
Although the code in Example 10(a) comes pretty close
to achieving the strong exception-safety guarantee, there's still one minor
quibble, as illustrated by the following client code:
String theName;
theName = f();
The String
copy constructor is invoked because the result is returned by value, and the
copy assignment operator is invoked to copy the result into theName.
If either copy fails, then f()
has completed all of its work and all of its side effects (good), but the result
has been irretrievably lost (oops).
Can we do better, and perhaps avoid the problem by
avoiding the copy? For example, we could
let the function take a non-const
String
reference parameter and place the return value in that:
// Example 10(b): Better?
//
void f( String& result )
{
cout << "some output";
result = "some value";
}
This may look better, but it isn't, because the
assignment to result
might still fail which leaves us with one side effect complete and the other
incomplete. Bottom line, this attempt doesn't really buy us much.
One way to solve the problem is to return a pointer to
a dynamically allocated String,
but the best solution is to go a step farther and return the pointer in an auto_ptr:
// Example 10(c): Correct (finally!)
//
auto_ptr<String> f()
{
auto_ptr<String> result = new String;
*result = "some value";
cout << "some output";
return result; // rely on transfer of ownership;
// this can't throw
}
This does the trick, since we have effectively hidden
all of the work to construct the second side effect (the return value) while
ensuring that it can be safely returned to the caller using only nonthrowing
operations after the first side effect has completed (the printing of the
message). We know that, once the cout
is complete, the returned value will make it successfully into the hands of the
caller, and be correctly cleaned up in all cases: If the caller accepts the
returned value, the act of accepting a copy of the auto_ptr
causes the caller to take ownership; and if the caller does not accept the
returned value, say by ignoring the return value, the allocated String
will be automatically cleaned up as the temporary auto_ptr
holding it is destroyed. The price for this extra safety? As often happens when implementing strong
exception safety, the strong safety comes at the (usually minor) cost of some
efficiency--here, the extra dynamic memory allocation. But, when it comes to
trading off efficiency for correctness, we usually ought to prefer the
latter!
Make a habit of using smart pointers like auto_ptr
in your daily work. auto_ptr
neatly solves common problems and will make your code safer and more robust,
especially when it comes to preventing resource leaks and ensuring strong
exception safety. Because it's standard, it's portable across libraries and
platforms, and so it will be right there with you wherever you take your
code.
Acknowledgments
This article is drawn from material in the new book Exceptional C++: 47 engineering puzzles, programming
problems, and exception-safety solutions by Herb Sutter, © 2000 Addison
Wesley Longman Inc., which contains further detailed treatments of points
touched on briefly in this article, including exception safety, the Pimpl
(compiler-firewall) Idiom, optimization, const-correctness,
namespaces, and other C++ design and programming topics.
Notes
1. The Pimpl Idiom is useful for reducing project build times
because it prevents wide-ranging recompilations of client code whenever the
private portions of C
change. For more about the Pimpl Idiom and how best to deploy compiler
firewalls, see Items 26 to 30 in the book Exceptional C++
(Addison-Wesley, 2000).
2. See the article Exception-Safe Generic Containers on the
Effective C++ CD (Scott Meyers,
Addison-Wesley, 1999) and Items 8 to 19 in Exceptional C++
(Herb Sutter, Addison-Wesley, 2000).