Scott Meyers
Update on auto_ptr
Last Updated January 31, 2007
by Scott Meyers
Between the time More Effective C++ was originally published at the end of 1995 and the time the ISO standard for C++ was completed in 1997, the specification for auto_ptr was revised twice. Implementations of each specifcation were made available and were used, so by 1998, there were three different versions of auto_ptr in existence:
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. | |
Version 3: | From November 1997 to the present. The Version 3 auto_ptr is what made it into the final ISO 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 noted below, the auto_ptr specification was modified slightly in 2003 to address a bug report, but that change didn't affect auto_ptr's fundamental behavior. |
As the specification for auto_ptr evolved, I updated More Effective C++ in new printings to track the latest specification. If you're interested in specific changes I made to the book as auto_ptr changed, 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. |
Since the ISO standard for C++ was adopted, auto_ptr has received continuing attention, because additional shortcomings in its specification and behavior have been discovered. In the sections that follow, I provide details on the current specifcation for auto_ptr, on the specifications that preceded it, and on concerns about the current specification.
Current Specification
The ISO standard for C++ specifies the following interface for auto_ptr. (This is copied straight out of the 2003 standard.)
namespace std { template <class Y> struct auto_ptr_ref {}; template<class X> class auto_ptr { 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& operator=(auto_ptr_ref<X> r) 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.
Why did auto_ptr change so much?
Conceptually, auto_ptr is extremely simple:
An auto_ptr is an object that acts just like a pointer, except it automatically deletes what it points to when it (the auto_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. Arguments over auto_ptr were so contentious, they threatened to delay adoption of the international language standard. In the end, even the final auto_ptr specification is not without its problems (as you'll see below).
Version 1 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; };
From Version 1 to Version 2
In March 1996, the 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_ptr<int> f(); // f returns an auto_ptr auto_ptr<int> p1(f()); // error! can't copy an auto_ptr returned // from a function auto_ptr<int> 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 with Version 1 auto_ptr is that
- Objects returned from functions are temporary objects, and temporary
objects can't be bound to reference parameters unless the parameters are
references to
const
objects. (For details, see Item 19 of More Effective C++.) - Version 1 auto_ptr's copy constructor and copy assignment operator take
reference-to-non-
const
parameters.
Hence, auto_ptrs 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, 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_ptrs 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 Version 2 auto_ptr as an excerpt from a March 1996 newsgroup posting by Greg Colvin. Greg has been a driving force behind the work on auto_ptr.
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; } };
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=
.
From Version 2 to Version 3
Rather than describe the shortcomings of Version 2 and how they were addressed in Version 3, I'll 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:
- Herb makes no mention of Version 1 auto_ptr at all. Anytime Herb refers to "old" auto_ptr behavior, that means Version 2 behavior. 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.
- The standardization meeting that replaced the Version 2 specification for 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.
For more information about the changes between versions 2 and 3, consult the final pre-standardization auto_ptr-related proposal that was adopted by the committee.
Post-Standardization Concerns
In the years since the C++ standard was adopted in 1998, several "issues" have been officially raised within or submitted to the standardization committee. At a high level, they can be summarized as "auto_ptr doesn't behave the way the committee expected when it approved the auto_ptr specification." One such issue was resolved in the 2003 Technical Corrigenda that specified "bug-fix" revisions to the C++ standard. It involved conversions between auto_ptrs and auto_ptr_refs, and you can read about it in Library Defect Report #127.
A more serious issue with auto_ptr is that it fails to support derived-to-base conversions in some situations — even situations it was specifically designed to allow. The following code, for example, is taken from the final pre-standardization auto_ptr document, where it was given as an example of valid code, yet it's since become clear that the code is not valid (and my experience is that most compilers reject it):
struct Base {}; // base class struct Derived : Base {}; // derived class auto_ptr<Derived> source(); // function returning an auto_ptr<Derived> void sink( auto_ptr<Base> ); // function taking an auto_ptr<Base> int main() { sink( source() ); // pass auto_ptr<Derived> to function } // expecting auto_ptr<Base>
Detailed examinations of this and other issues can be found in a committee paper examining the problem and in Library Defect Report #463.
Discussion of what the future may hold for auto_ptr can be found in a February 2006 comp.std.c++ discussion of the topic.