Resource Acquisition Is Initialization (RAII)


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


// is this correct?
void f()
{
    char *cp = new char[1024];

    g(cp);
    h(cp);

    delete [] cp;
}


// is this nice?
void f()
{
    char *cp = new char[1024];

    try
    {
        g(cp);
        h(cp);
        delete [] cp;
    }
    catch( ... )
    {
        delete [] cp;
        throw;
    }
}


// Stroustrup: resource allocation is initialization
struct Res
{
    Res(int n) { cp = new char[n]; }
    ~Res() { delete [] cp; }
    char *getcp() const { return cp; }
};

void f()
{
    Res res(1024);

    g(res.getcp());
    h(res.getcp());
}


// But be care:
struct BadRes
{
    Res(int n) { cp = new char[n]; ... throw n; ... }
    ~Res()     { delete [] cp; }
    ...
};



We solve these problems with smart pointers for memory resources.
The main issue with smart pointers is that who have to free the resources.
Two main strategies exist for smart pointers:

- ownership
- reference count


Auto_ptr

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
*/



Quiz question:


class Base { };
class Derived : public Base { };

Derived *dp = new Derived;
auto_ptr<Derived> da(new Derived);

Base *bp = dp;            // ok
auto_ptr<Base> ba = da    // ???



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;
            }
        }



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...
    // unique(), remove(), 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;
    }
};


Quiz

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



Arrays and Auto_ptr

Auto_ptr is not able to manage arrays, because the destructor wired-in
to call non-array delete.


void f(int n)
{
    auto_ptr<int> p1(new int);
    auto_ptr<int> p2(new int[n]);
    ...
   // delete  and not  delete[]  for  p2
}


Array Delete Adapter is a solution to manage arrays by auto_ptr.


#include <memory>
#include <iostream>
#include <vector>

template <typename T>
class ArrDelAdapter
{
public:
    ArrDelAdapter(T *p) : p_(p) { }
    ~ArrDelAdapter() { delete [] p_; }
    // operators like ->, *, etc...
private:
    T* p_;
};

struct X
{
    X()  { std::cout << "X()"  << std::endl; }
    ~X() { std::cout << "~X()" << std::endl; }
};

int main()
{
    std::auto_ptr< ArrDelAdapter<X> > pp(new ArrDelAdapter<X>(new X[10]));
    return 0;
}