Effective C++ 筆記 —— Item 13: Use objects to manage resources.
Consider this code:
class Investment { /*...*/ }; // root class of hierarchy of investment types Investment* createInvestment(); // return ptr to dynamically allocated object in the Investment hierarchy; // the caller must delete it (parameters omitted for simplicity) voidf() { Investment *pInv = createInvestment(); // call factory function use pInv // ... delete pInv; // release object }
This looks okay, but there are several ways f could fail to delete the investment object it gets from createInvestment.
Here's how to use auto_ptr to prevent f's potential resource leak:
void f() { std::auto_ptr<Investment> pInv(createInvestment()); // call factory function use pInv as before automatically delete pInv via auto_ptr’s dtor // ... }
Because an auto_ptr automatically deletes what it points to when the auto_ptr is destroyed, it’s important that there never be more than one auto_ptr pointing to an object. If there were, the object would be deleted more than once, and that would put your program on the fast track to undefined behavior. To prevent such problems, auto_ptrs have an unusual characteristic: copying them (via copy constructor or copyassignment operator) sets them to null, and the copying pointer assumes sole ownership of the resource!
std::auto_ptr<Investment> // pInv1 points to the object returned from createInvestment pInv1(createInvestment()); std::auto_ptr<Investment> pInv2(pInv1); // pInv2 now points to the object; pInv1 is now null object, and pInv2 is null pInv1 = pInv2; // now pInv1 points to the
An alternative to auto_ptr is a reference-counting smart pointer (RCSP). An RCSP is a smart pointer that keeps track of how many objects point to a particular resource and automatically deletes the resource when nobody is pointing to it any longer. As such, RCSPs offer behavior that is similar to that of garbage collection. Unlike garbage collection, however, RCSPs can’t break cycles of references (e.g., two otherwise unused objects that point to one another).
void f() { //... std::tr1::shared_ptr<Investment> pInv(createInvestment()); // call factory function use pInv as before automatically delete pInv via shared_ptr’s dtor //... }
void f() { // ... std::tr1::shared_ptr<Investment> // pInv1 points to the object returned from createInvestment pInv1(createInvestment()); std::tr1::shared_ptr<Investment> pInv2(pInv1); // both pInv1 and pInv2 now point to the object pInv1 = pInv2; // ditto — nothing has changed ... } // pInv1 and pInv2 are destroyed, and the object they point to is automatically deleted
Both auto_ptr and tr1::shared_ptr use delete in their destructors, not delete [].That means that using auto_ptr or tr1::shared_ptr with dynamically allocated arrays is a bad idea.
You may be surprised to discover that there is nothing like auto_ptr or tr1::shared_ptr for dynamically allocated arrays in C++, not even in TR1. That’s because vector and string can almost always replace dynamically allocated arrays. If you still think it would be nice to have auto_ptr- and tr1::shared_ptr-like classes for arrays, look to Boost.
Things to Remember:
- To prevent resource leaks, use RAII objects that acquire resources in their constructors and release them in their destructors.
- Two commonly useful RAII classes are tr1::shared_ptr and auto_ptr. tr1::shared_ptr is usually the better choice, because its behavior when copied is intuitive. Copying an auto_ptr sets it to null.