Resource Acquisition Is Initialization (RAII)
Various memory allocation problems can occur connected with exception
handling and other language features.
void f()
{
char *cp = new char[1024];
g(cp);
h(cp);
delete [] cp;
}
void f()
{
char *cp = new char[1024];
try
{
g(cp);
h(cp);
delete [] cp;
}
catch( ... )
{
delete [] cp;
throw;
}
}
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());
}
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;
p = q;
cout << "after change and reassignment:" << endl;
cout << " p: " << *p << endl;
cout << " q: " << *q << endl;
}
Quiz question:
class Base { };
class Derived : public Base { };
Derived *dp = new Derived;
auto_ptr<Derived> da(new Derived);
Base *bp = dp;
auto_ptr<Base> ba = da
The Definition of Auto_ptr
namespace std {
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;
public:
typedef T element_type;
explicit auto_ptr (T* ptr = 0) throw() : ap(ptr) { }
auto_ptr (auto_ptr& rhs) throw() : ap(rhs.release()) { }
template<class Y>
auto_ptr (auto_ptr<Y>& rhs) throw() : ap(rhs.release()) { }
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;
}
~auto_ptr() throw()
{
delete ap;
}
T* get() const throw()
{
return ap;
}
T& operator*() const throw()
{
return *ap;
}
T* operator->() const throw()
{
return ap;
}
T* release() throw()
{
T* tmp(ap);
ap = 0;
return tmp;
}
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));
auto_ptr<int> p2 = new int(2);
There is no pointer arithmetic
*p2 = 5;
++p2;
Be care with ownership strategy:
auto_ptr<int> p1(new int(1));
auto_ptr<int> p2(new int(2));
p2 = p1;
Be care: copies of auto_ptr are not equivalent:
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;
To avoid this mistakes you can ensure the keeping of ownership:
const auto_ptr<int> p(new int), q;
*p = 4;
print(p);
*p = 5;
q = p;
Auto_ptr is not situable for STL containers:
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() );
}
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.
auto_ptr<int> source(int n)
{
auto_ptr<int> p(new int(n));
...
return p;
}
void sink( auto_ptr<int>) { };
auto_ptr<int> p1 = source(1);
sink( p1 );
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]);
...
}
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_; }
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;
}