Various memory allocation problems can occur connected with exception handling and other language features.
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 */
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... }
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
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=()?