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

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

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.