Auto_ptr class

Various memory allocation problems can occur connected with exception handling and other language features.

Usage of Auto_ptr class

Auto_ptr is a ownership-based smart-pointer. Copy constructor and assignment transfers the ownership.


int main()
{
    auto_ptr<int> p(new int(42));
    auto_ptr<int> q;

    cout << "after initialization:" << endl;
    cout << " p: " << p << endl;
    cout << " q: " << q << endl;

    q = p;
    cout << "after assigning auto pointers:" << endl;
    cout << " p: " << p << endl;
    cout << " q: " << q << endl;

    *q += 13;                   // change value of the object q owns
    p = q;
    cout << "after change and reassignment:" << endl;
    cout << " p: " << p << endl;
    cout << " q: " << q << endl;
}

/*
after initialization:
 p: 42
 q: NULL
after assigning auto pointers:
 p: NULL
 q: 42
after change and reassignment:
 p: 55
 q: NULL
*/

The Definition of Auto_ptr


namespace std {
    // auxiliary type to enable copies and assignments (now global)
    template<class Y>
    struct auto_ptr_ref {
        Y* yp;
        auto_ptr_ref (Y* rhs)
         : yp(rhs) {
        }
    };

    template<class T>
    class auto_ptr
    {
    private:
        T* ap;    // refers to the actual owned object (if any)
    public:
        typedef T element_type;

        // constructor
        explicit auto_ptr (T* ptr = 0) throw() : ap(ptr) { }

        // copy constructors (with implicit conversion)
        // - note: nonconstant parameter
        auto_ptr (auto_ptr& rhs) throw() : ap(rhs.release()) { }

        template<class Y>
        auto_ptr (auto_ptr<Y>& rhs) throw() : ap(rhs.release()) { }

        // assignments (with implicit conversion)
        // - note: nonconstant parameter
        auto_ptr& operator= (auto_ptr& rhs) throw()
        {
            reset(rhs.release());
            return *this;
        }
        template<class Y>
        auto_ptr& operator= (auto_ptr<Y>& rhs) throw()
        {
            reset(rhs.release());
            return *this;
        }

        // destructor
        ~auto_ptr() throw()
        {
            delete ap;
        }

        // value access
        T* get() const throw()
        {
            return ap;
        }
        T& operator*() const throw()
        {
            return *ap;
        }
        T* operator->() const throw()
        {
            return ap;
        }

        // release ownership
        T* release() throw()
        {
            T* tmp(ap);
            ap = 0;
            return tmp;
        }

        // reset value
        void reset (T* ptr=0) throw()
        {
            if (ap != ptr)
            {
                delete ap;
                ap = ptr;
            }
        }

Problems with Auto_ptr

There are a number of issues connected to auto_ptr class.

Auto_ptr has explicit constructor:


auto_ptr<int>  p1(new int(1));  // Ok
auto_ptr<int>  p2 = new int(2); // Error, explicit constructor

There is no pointer arithmetic


 *p2 = 5;   // Ok
++p2;       // Error, no pointer arithmetic

Be care with ownership strategy:


auto_ptr<int>  p1(new int(1));
auto_ptr<int>  p2(new int(2));

p2 = p1;        // delete 2, transfers ownership from p1

Be care: copies of auto_ptr are not equivalent:


// is this correct?
template <class T>
void print( auto_ptr<T> p)
{
    if ( p.get() )
        cout << *p;
    else
        cout << "null";
}
//...
auto_ptr<int>  p(new int);
*p = 4;
print(p);
*p = 5;     // runtime error
//...

To avoid this mistakes you can ensure the keeping of ownership:


// keep ownership by const auto_ptr
const auto_ptr<int>  p(new int), q;
*p = 4;
print(p);   // compile-time error: cannot change ownership
*p = 5;     // ok: const of ownership
 q = p;     // compile-time error: q const

Auto_ptr is not situable for STL containers:


// is this correct?
void f()
{
    vector< auto_ptr<int> >  v;

    v.push_back( auto_ptr<int>( new int(1) ) );
    v.push_back( auto_ptr<int>( new int(4) ) );
    v.push_back( auto_ptr<int>( new int(3) ) );
    v.push_back( auto_ptr<int>( new int(2) ) );

    sort( v.begin(), v.end() ); // ??? what about pivot element, etc...
}

Memory Handling Strategies

There is a so-called: source-sink strategy using auto_ptr class.

  • Source is a function, which creates new memory resource and transfers it back to the caller.

  • Sink is a function, which gets the ownership from its caller, and removes the memory resource.


//  source and sink
auto_ptr<int> source(int n)
{
    auto_ptr<int> p(new int(n));
    ...
    return p;       // transfers ownership to the caller
}

void sink( auto_ptr<int>) { };

auto_ptr<int>  p1 = source(1);  // creat, get ownership
//...
sink( p1 );     // gets ownership and delete 1 on return

Auto_ptr as Class Member

Suppose we have a class A, which has an aggregated data type B with two objects. For lifetime reasons, these objects are created outside of A, and the aggregation is implemented by pointers.


class B;

class A
{
private:
    B *p1;
    B *p2;
public:
    A( B b1, B b2) : p1(new B(b1)), p2(new B(b2))  {  }
    A( const A& rhs) : p1( new B(*rhs.p1)), p2( new B(*rhs.p2))  {  }
    A& operator=(const A& rhs)
    {
        *p1 = *rhs.p1;
        *p2 = *rhs.p2;
        return *this;
    }
    ~A()
    {
        delete p1;
        delete p2;
    }
};

The previous class has serious memory problems. Using auto_ptr the previous memory leaks are disappeared.


class A
{
private:
    const auto_ptr<B> p1;
    const auto_ptr<B> p2;
public:
    A( B b1, B b2) : p1(new B(b1)), p2(new B(b2))  {  }
    A( const A& rhs) : p1( new B(*rhs.p1)), p2( new B(*rhs.p2))  {  }
    A& operator=(const A& rhs)
    {
        *p1 = *rhs.p1;
        *p2 = *rhs.p2;
        return *this;
    }
};

We do not need destructor any more. Why we still need copy constructor and operator=()?