auto_ptr update

Last Modified December 15, 1999

Background

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.

Current Status

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.

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. 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).

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 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_ptr f();        // 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

  1. 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++.)
  2. auto_ptr's copy constructor and assignment operator take reference-to-non-const parameters (see Version 1 definition of auto_ptr above)
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, 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_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 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=.

From Version 2 to Version 3

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:

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.