FAQ 30.12 Does auto_ptr enforce the Law of the Big Three and solve the problems associated with remote ownership?
When a class uses a plain T* to implement remote ownership, forgetting any of the Big Three causes the compiler to silently generate wrong code. The result is often a disaster at runtime. Unfortunately, replacing the T* with a managed pointer such as auto_ptr<T> does not correct the problem. The root of the problem is that when an auto_ptr<T> is copied, ownership of the referent is transferred to the copy and the original object's auto_ptr<T> becomes NULL. This is often undesirable. What is needed instead is for the referent to be copied or for a compile-time error to be generated that flags the problem. The safest solution is to define and use a strict auto_ptr<T>. For example, the following could go into file strict_auto_ptr.h and could be reused whenever anyone wanted a strict auto_ptr<T>. Note that the copy constructor and assignment operator are private: and are undefined, thus making it impossible to copy a strict_auto_ptr<T> object. #include <memory> using namespace std; template<class T> class strict_auto_ptr : public auto_ptr<T> { public: strict_auto_ptr(T* p=NULL) throw() : auto_ptr<T>(p) { } private: strict_auto_ptr(const strict_auto_ptr&) throw(); void operator= (const strict_auto_ptr&) throw(); }; When strict_auto_ptr<T> is used, the compiler either synthesizes the Big Three correctly or causes specific, compile-time errors; it does not allow run-time disasters. The following example shows a class that implements remote ownership by the managed pointer strict_auto_ptr<Noisy> rather than the plain pointer Noisy*. #include <iostream> using namespace std; class Noisy { public: Noisy() throw(); ~Noisy() throw(); Noisy& operator= (const Noisy&) throw(); Noisy (const Noisy&) throw(); }; Noisy::Noisy() throw() { cout << "Noisy::Noisy()\n"; } Noisy::~Noisy() throw() { cout << "Noisy::~Noisy()\n"; } typedef strict_auto_ptr<Noisy> NoisyPtr; class Fred { public: Fred() throw(bad_alloc); //No destructor needed: The Noisy will automagically get //deleted. The compiler won't synthesize a copy ctor or //assignment operator, since the strict_auto_ptr version of these //are private. protected: NoisyPtr ptr_; // Like Noisy* ptr_ but better }; Fred::Fred() throw(bad_alloc) : ptr_(new Noisy()) { } void sample() { Fred x; //OK: Allocates a new Noisy } //OK: x is destructed, so its Noisy is deleted int main() { sample(); } Because strict_auto_ptr<Noisy>'s destructor deletes the referent, Fred doesn't need an explicit destructor. The Fred::~Fred() synthesized by the compiler is correct. Because strict_auto_ptr<Noisy>'s copy constructor and assignment operator are private:, the compiler is prevented from synthesizing either the copy constructor or the assignment operator for class Fred. Copying or assigning a Fred produces a specific, compile-time error message. Compare this to using a Noisy*, in which case the compiler silently synthesizes the wrong code, producing disastrous results. For example, when the GENERATE_ERROR symbol is #defined in the following function, the compiler gives an error message rather than silently doing the wrong thing. void disasterAverted(const Fred& x) throw() { #ifdef GENERATE_ERROR Fred y = x; //gives a compile-time error message y = x; //gives a compile-time error message #endif } strict_auto_ptr<T> effectively automates the proper delete and prevents the compiler from synthesizing improper copy operations. It plugs leaks and enforces the Law of the Big Three. |