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