Last Modified December 15, 1999
Discussions of auto_ptr can be confusing. Since More Effective C++ was
originally published at the end of 1995, the specification for auto_ptr
was
revised twice. As a result, there have been three specifications for
auto_ptr
during the lifetime of the book:
Version 1: Prior to March 1996. This is sometimes referred to as the CD-1 version, because this is how auto_ptr was specified in the ISO draft standard known as CD-1. Version 2: From March 1996 until November 1997. This is sometimes referred to as the CD-2 version. Perhaps you can guess why... Version 3: From November 1997 to the present. The Version 3 auto_ptr is what made it into the final ISO/ANSI standard for C++. It's also sometimes known as the FDIS version, because the FDIS ("Final Draft International Standard") was the last draft prior to official standardization.
As the specification for auto_ptr evolved, I updated More Effective C++ in
new printings to track the latest specification. Ironically, the Version 3
auto_ptr is conceptually identical to the Version 1 auto_ptr
I originally
described! If you care about specific changes I made to the book as
auto_ptr
evolved, consult the book's modification history.
Behaviorially, the versions can be summarized this way:
Version 1: Only one auto_ptr is allowed to point to an object. Copying an auto_ptr sets its internal dumb pointer to null. Version 2: Multiple auto_ptrs may point to an object, but exactly one is designated the "owner". When the owning auto_ptr is destroyed, it deletes what it points to. When non-owning auto_ptrs are destroyed, nothing happens to the object they point to. Copying an auto_ptr transfers ownership from the copied object to the copying object. Version 3: Same as Version 1, but the C++ interface was modified to prevent certain unanticipated and unwanted behavioral anomalies that were present in Version 1.
The ISO/ANSI standard for C++ specifies the following class interface for
auto_ptr
:
namespace std { template<class X> class auto_ptr { template<class Y> struct auto_ptr_ref {}; public: typedef X element_type; // 20.4.5.1 construct/copy/destroy: explicit auto_ptr(X* p=0) throw(); auto_ptr(auto_ptr&) throw(); template<class Y> auto_ptr(auto_ptr<Y>&) throw(); auto_ptr& operator=(auto_ptr&) throw() template<class Y> auto_ptr& operator=(auto_ptr<Y>&) throw(); ~auto_ptr() throw(); // 20.4.5.2 members: X& operator*() const throw(); X* operator->() const throw(); X* get() const throw(); X* release() throw(); void reset(X* p=0) throw(); // 20.4.5.3 conversions: auto_ptr(auto_ptr_ref<X>) throw(); template<class Y> operator auto_ptr_ref <Y>() throw(); template<class Y> operator auto_ptr<Y>() throw(); }; }
The standard also includes specifications for the semantics of each member
function, but you should be able to figure out what each does by consulting
the material describing how Version 2 auto_ptr
became Version 3.
As it turns out, the dust hasn't quite settled on auto_ptr, because some minor errors have been detected in the standard's specification: it doesn't quite say what the standardization committee wanted it to say. When everything is taken care of, I'll update this page again to let you know the details. (If you just can't wait that long, search for "auto_ptr" in the Active Issues List for the C++ Standard Library.)
In the meantime, here's a question you may find relevant: What does your compiler support? The answer is simple: it varies. Different compilers ship with different versions of the standard C++ library, and I suspect you could find compilers shipping with any of the three definitions of auto_ptr if you looked around enough. Furthermore, all three definitions make use of member function templates, and some compilers still fail to supportb those. Such compilers must therefore ship with an approximation to one of the versions above.
What kind of support does your compiler and library offer for auto_ptr?
The only reliable way to know is to look for yourself. A good place to
start looking is the header <memory>, because that's where the C++ standard
says the auto_ptr
template is to be defined.
auto_ptr
change so much?
Conceptually, auto_ptr
is extremely simple:
Anauto_ptr
is an object that acts just like a pointer, except it automatically deletes what it points to when it (theauto_ptr
) is destroyed.
Unfortunately, specifying an interface for auto_ptr such that it behaves in
a reasonable fashion is extremely difficult. The people on the C++
standardization committee struggled with it for years before they finally
came up with something that was agreeable to everybody. In fact, arguments
over auto_ptr
were so contentious, they threatened to delay adoption of the
international language standard. Think of what that means. The final
standard for C++ is some 700 pages long, yet little auto_ptr
(whose
complete specification -- including semantics -- takes up only about 135
lines) nearly held things up. It's actually quite remarkable.
The issues behind auto_ptr
are both interesting and instructive. I've
toyed with the notion of writing an article called "The Strange Tale of
auto_ptr" to discuss them, but I just don't have the time. (If you have
the time, I encourage you to write the article!) However, the material
that follows should help you understand the shortcomings of Versions 1 and
2 that ultimately led to Version 3 (i.e., standard auto_ptr
).
auto_ptr
This is how auto_ptr
was specified in CD-1, i.e., at the time I originally
wrote More Effective C++. I've copied this out of the initial printing of
the book; note that I've omitted the fact that auto_ptr
is in the
namespace std.
template<class T> class auto_ptr { public: explicit auto_ptr(T *p = 0); // see Item 5 for a // description of "explicit" template<class U> // copy constructor member auto_ptr(auto_ptr<U>& rhs); // template (see Item 28): // initialize a new auto_ptr // with any compatible // auto_ptr ~auto_ptr(); template<class U> // assignment operator auto_ptr<T>& operator=(auto_ptr<U>& rhs); // member template (see // Item 28): assign from any // compatible auto_ptr T& operator*() const; // see Item 28 T* operator->() const; // see Item 28 T* get() const; // return value of current // dumb pointer T* release(); // relinquish ownership of // current dumb pointer and // return its value void reset(T *p = 0); // delete owned pointer; // assume ownership of p private: T *pointee; };
In March 1996, the ISO/ANSI committee standardizing C++ modified the
definition of auto_ptr
to address the fact that the Version 1 definition
prevented almost any use of an auto_ptr
returned from a function. For
example:
auto_ptrf(); // f returns an auto_ptr auto_ptr p1(f()); // error! can't copy an auto_ptr returned // from a function auto_ptr p2; p2 = f(); // error! can't use an auto_ptr returned // from a function as the source of an assignment
The reason the statements above won't compile is that
auto_ptr
's copy constructor and assignment operator take
reference-to-non-const parameters (see Version 1 definition of
auto_ptr
above)
auto_ptr
s returned from functions could be neither copied nor used
as the source of assignments. That was a serious limitation. I was aware
of this limitation when I wrote More Effective C++, but I also knew that
the standardization committee was likely to resolve the problem, so I said
nothing about it in the book.
Conceptually, the Version 2 auto_ptr
behaved similarly to Version 1.
auto_ptr objects still transferred ownership when they were copied, and
they still deleted what they owned when they were destroyed. However,
while the Version 1 auto_ptr
set its internal dumb pointer to null whenever
it relinquished ownership, Version 2 transferred ownership without changing
its internal dumb pointer. Thus, it was possible for multiple auto_ptr
s to
point to the same object. However, only one of them owned what it pointed
to, and only one had the right to delete it.
Here is a sample implementation of the Version 2 auto_ptr. It was posted
to comp.std.c++ on March 30, 1996, by Greg Colvin. Greg was a driving
force behind the work on a standardized auto_ptr
up until the very end.
[At the March ANSI/ISO committee meeting] we decided to change the auto_ptr copy semantics to allow returns of auto_ptr from functions. Following is a simple implementation. ... Note that on most architectures the owner bit can be overlayed with the pointer for a smaller footprint, but the following is portable. template<class X> class auto_ptr { mutable bool owner; X* px; template<class Y> friend class auto_ptr; public: explicit auto_ptr(X* p=0) : owner(p), px(p) {} template<class Y> auto_ptr(const auto_ptr<Y>& r) : owner(r.owner), px(r.release()) {} template<class Y> auto_ptr& operator=(const auto_ptr<Y>& r) { if ((void*)&r != (void*)this) { if (owner) delete px; owner = r.owner; px = r.release(); } return *this; } ~auto_ptr() { if (owner) delete px; } X& operator*() const { return *px; } X* operator->() const { return px; } X* get() const { return px; } X* release() const { owner = 0; return px; } };
The Version 2 auto_ptr
also eliminated the "reset" member function found in
Version 1. That function was removed because it was considered unsafe and
redundant with operator=
.
Rather than describe the shortcomings of Version 2 and how they were addressed in Version 3, I'll simply refer you to Herb Sutter's solution to Guru of the Week #25, which is devoted to the topic. You'll find Herb's discussion easier to understand if you keep the following in mind:
auto_ptr
at all. Anytime the
document refers to "old" auto_ptr
behavior, that means Version 2
behavior. Remember that at the time Herb wrote the GOTW solution,
Version 2 auto_ptr
had been current for over 18 months. By that time,
Version 1 auto_ptr
was ancient history.
auto_ptr
with Version 3 was held in New Jersey. Herb often refers
to auto_ptr
behavior "Before NJ" and "After NJ". The former means
Version 2 (i.e., CD-2) behavior, the latter means Version 3 (FDIS)
behavior.
If you want still more information after reading the GOTW #25 solution,
feast your eyes on this, the final auto_ptr
-related proposal that was
adopted by the C++ standardization committee. I thank authors Bill Gibbons
and Greg Colvin for allowing me to include it here.
Doc. No.: J16/97-0090R1=WG21/N1128R1 Date: November 14, 1997 Project: Programming Language C++ Reply to: Bill Gibbons <bill@gibbons.org> Greg Colvin <greg@imrgold.com> Fixing auto_ptr. The auto_ptr specified in CD-2 has proved unpopular and dangerous, primarily because the const arguments to its copy operations make it easy to inadvertently damage an auto_ptr via a const reference, and because the non-owning pointer left behind by a copy is an open invitation to dangling references. The auto_ptr& arguments to the copy constructor and assignment operator were not const in the CD-1 auto_ptr, but were made const to allow auto_ptr values to be passed to and returned from functions. The C++ language now allows a more effective solution. We propose to restore the CD-1 auto_ptr semantics by: - removing const from the arguments to all copy operations and from the release() function; - restoring the pointer-zeroing effect of release(); - restoring the reset() function; and - adding conversion functions and a private auxiliary class to allow auto_ptr rvalues to convert to lvalues. Draft text to replace 20.4.5 follows. 20.4.5 Template class auto_ptr 1 Template auto_ptr holds a pointer to an object obtained via new and deletes that object when it itself is destroyed (such as when leaving block scope 6.7). 2 Template auto_ptr_ref holds a reference to an auto_ptr. It is used by the auto_ptr conversions to allow auto_ptr objects to be passed to and returned from functions. namespace std { template<class X> class auto_ptr { template<class Y> struct auto_ptr_ref {}; public: typedef X element_type; // 20.4.5.1 construct/copy/destroy: explicit auto_ptr(X* p=0) throw(); auto_ptr(auto_ptr&) throw(); template<class Y> auto_ptr(auto_ptr<Y>&) throw(); auto_ptr& operator=(auto_ptr&) throw() template<class Y> auto_ptr& operator=(auto_ptr<Y>&) throw(); ~auto_ptr() throw(); // 20.4.5.2 members: X& operator*() const throw(); X* operator->() const throw(); X* get() const throw(); X* release() throw(); void reset(X* p=0) throw(); // 20.4.5.3 conversions: auto_ptr(auto_ptr_ref<X>) throw(); template<class Y> operator auto_ptr_ref <Y>() throw(); template<class Y> operator auto_ptr<Y>() throw(); }; } 3 The auto_ptr provides a semantics of strict ownership. An auto_ptr owns the object it holds a pointer to. Copying an auto_ptr copies the pointer and transfers ownership to the destination. If more than one auto_ptr owns the same object at the same time the behavior of the program is undefined. 20.4.5.1 auto_ptr constructors explicit auto_ptr(X* p =0) throw(); 1 Postconditions: *this holds the pointer p. auto_ptr(auto_ptr& a) throw(); 2 Effects: Calls a.release(). 3 Postconditions: *this holds the pointer returned from a.release(). template<class Y> auto_ptr(auto_ptr<Y>& a) throw(); 4 Requires: Y* can be implicitly converted to X*. 5 Effects: Calls a.release(). 6 Postconditions: *this holds the pointer returned from a.release(). auto_ptr& operator=(auto_ptr& a) throw(); 7 Requires: The expression delete get() is well formed. 8 Effects: reset(a.release()). 9 Returns: *this. template<class Y> auto_ptr& operator=(auto_ptr<Y>& a) throw(); 10 Requires: Y* can be implicitly converted to X*. The expression delete get() is well formed. 11 Effects: reset(a.release()). 12 Returns: *this. ~auto_ptr() throw(); 13 Requires: The expression delete get() is well formed. 14 Effects: delete get(). 20.4.5.2 auto_ptr members X& operator*() const throw(); 1 Requires: get() != 0 2 Returns: *get() X* operator->() const throw(); 3 Requires: get() != 0 4 Returns: get() X* get() const throw(); 5 Returns: The pointer *this holds. X* release() throw(); 6 Returns: get() 7 Postconditions: *this holds the null pointer. void reset(X* p=0) throw(); 8 Effects: If get() != p then delete get(). 9 Postconditions: *this holds the pointer p. 20.4.5.3 auto_ptr conversions auto_ptr(auto_ptr_ref<X> r) throw(); 1 Effects: Calls p->release() for the auto_ptr p that r holds. 2 Postconditions: *this holds the pointer returned from release(). template<class Y> operator auto_ptr_ref <Y>() throw(); 3 Returns: An auto_ptr_ref<Y> that holds *this. template<class Y> operator auto_ptr<Y>() throw(); 4 Effects: Calls release(). 5 Returns: An auto_ptr<Y> that holds the pointer returned from release(). Analysis of Conversion operations There are four cases to consider: direct-initialization and copy- initialization (8.5/14) for both same-type initialization and base- from-derived initialization. (1) Direct-initialization, same type, e.g. auto_ptr<int> source(); auto_ptr<int> p( source() ); This is considered a direct call to a constructor of auto_ptr<int>,using overload resolution. There is only one viable constructor: auto_ptr<int>::auto_ptr(auto_ptr_ref<int>) which is callable using the conversion auto_ptr<int>::operator auto_ptr_ref<int>() which should be selected when operator overloading tries to convert type auto_ptr<int> to auto_ptr_ref<int>. Overload resolution succeeds. No additional copying is allowed, so the copy constructor need not be callable. (2) Copy-initialization, same type, e.g. auto_ptr<int> source(); void sink( auto_ptr<int> ); main() { sink( source() ); } According to 8.5/14: If the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same class as, or a derived class of, the class of the destination, constructors are considered... So this case is handled the same as the direct-initialization case. (3) Direct-initialization, base-from-derived, e.g. struct Base {}; struct Derived : Base {}; auto_ptr<Derived> source(); auto_ptr<Base> p( source() ); This is similar to (1); in this case, the viable constructor is: auto_ptr<Base>::auto_ptr(auto_ptr_ref<Base>) which is callable using the conversion auto_ptr<Derived>::operator auto_ptr_ref<Base>() which should be selected when operator overloading tries to convert type auto_ptr<Derived> to auto_ptr_ref<Base>. Overload resolution succeeds. No additional copying is allowed, so the copy constructor need not be callable. (4) Copy-initialization, base-from-derived, e.g. struct Base {}; struct Derived : Base {}; auto_ptr<Derived> source(); void sink( auto_ptr<Base> ); main() { sink( source() ); } This case is not similar to (2), because the sentence quoted above from 8.5/14 does not apply. So there must be a conversion function (operator or constructor) from the argument type to the parameter type, and it will be used to initialize a temporary variable. Note that this initialization process does not involve use of a copy constructor: The user-defined conversion so selected is called to convert the initializer expression into a temporary, whose type is the type returned by the call of the user-defined conversion function, with the cv-qualifiers of the destination type. The parameter type is auto_ptr<Base>, so there must be a conversion from auto_ptr<Derived> to auto_ptr<Base>. The constructor auto_ptr<Base>::auto_ptr<Derived>(auto_ptr<Derived> &) does not work because the argument is an rvalue. But the conversion function auto_ptr<Derived>::operator auto_ptr<Base>() does work. The result of calling this conversion function is a temporary - no copy constructor is needed. Once the temporary has been created, the draft says: The object being initialized is then direct-initialized from the temporary according to the rules above. This direct-initialization is case (1) which works. At no time in any of these four cases is the implementation allowed to make an unnecessary copy of an auto_ptr object. Therefore it does not matter that the copy constructor does not work on rvalues.