td::auto_ptr is the only smart pointer class available in C++98. Alas, since this class is neither assignable nor copy-constructible, creating containers of auto_ptr objects is illegal. This limitation has been a source of frustration and confusion for years. At last, the Library Extensions Technical Report 1 (TR1 for short) rectifies this embarrassment, adding a new smart pointer class called shared_ptr to the standard header
How to combine smart pointers with STL containers and algorithms without risking undefined behavior?
Use the std::tr1::shared_ptr class.
Presenting the Problem
A typical smart pointer wraps a raw pointer to a dynamically allocated object and overloads the operators -> and *. The responsibility for deleting the pointer is delegated to the smart pointer’s destructor, thus freeing you from manual?and bug-prone?memory management. The problem, however, is that auto_ptr, the only smart pointer class available in C++98, has peculiar copy and assignment semantics. When you assign an auto_ptr x to another auto_ptr y, not only is the target object modified (as expected) but so is the source object, which becomes null. This behavior makes auto_ptr incompatible with STL containers and algorithms. Let’s see how shared_ptr solves this problem.
Author’s Note: To use shared_ptr you need to download its sources from Boost. Notice that this Solution adheres to the official TR1 Draft Proposal, which differs slightly from boost::shared_ptr. |
Construction and Initialization
shared_ptr‘s default constructor creates what is known as an empty smart pointer, i.e., an object whose pointer is null. You can assign a valid pointer to it later:
class Foo{ int x;public: explicit Foo(int val=0) :x(val) {} void do_something() {/*..*/} int show_x() const {std::cout //for shared_ptrusing std::tr1::shared_ptr;int main(){ shared_ptr pf; //pf is an empty shared_ptr //bind a "live" pointer to pf pf=new Foo; pf->do_mesomething(); //using shared_ptr's overloaded -> (*pf).show_x();//using shared_ptr's overloaded *}//pf's destructor called here, destroying Foo
shared_ptr defines a conversion operator to bool. This is useful for testing whether the shared_ptr is empty. To access the raw pointer directly, call get():
if (pf) { //ensure that pf isn't empty before calling get Foo *palias = pf.get();}
The second shared_ptr constructor takes a pointer to a dynamically allocated object and an optional deleter. The following example defines a deleter and passes its address to shared_ptr’s constructor:
void my_deleter(Foo *ptr){ delete ptr; std::cout pf (new Foo, my_deleter);
A deleter can be any callable entity: a free function, a function object, etc. When a user-defined deleter d is provided, shared_ptr‘s destructor calls:
d(ptr);
Instead of deleting ptr directly. A custom deleter enables you to bind shared_ptr to a pointer that was allocated in a different DLL, for example.
Certain resources such as file descriptors, synchronization objects, and device contexts require that a special API function be called to release them. Using a custom deleter, shared_ptr can manage such resources elegantly. In the following example, shared_ptr holds a mutex pointer obtained from an API function. When the shared_ptr object is destroyed, the release_mutex() function is called automatically:
//API functions for acquiring and releasing a mutexmutex_t* get_mutex(pid_t pid);int release_mutex(mutex_t* pmx);shared_ptr pmutex (get_mutex(mypid), release_mutex);
Counting on Reference Counting
Each shared_ptr destructor call decrements the bound pointer’s reference count. When the reference count reaches 0, the pointer is deleted. This property enables you to combine shared_ptr with STL containers and algorithms. The following example of creating a heterogeneous container is based on my September 2000 10-Minute Solution. This version however uses shared_ptr instead of bare pointers.
First, review the class hierarchy:
class mutimedia_file {public: explicit mutimedia_file(const string& filename); virtual ~mutimedia_file(); int virtual play();//..};class wav_file : public mutimedia_file;class mp3_file : public mutimedia_file;class ogg_file : public mutimedia_file;
Here’s how you populate the vector with heterogeneous shared_ptrs:
typedef std::tr1::shared_ptr Pmm;typedef std::vector Vpmm;void fill_and_play(Vpmm & v) { Pmm temp(new mp3_file("crazy4u"));//#1 create shared_ptr v.push_back(temp);//#2 store a copy of temp in v //reuse temp temp.reset(new wav_file("email_alert")); #3 v.push_back(temp); // insert shared_ptr to v v[0]->play(); // mp3_file::play() v[1]->play(); // wav_file::play());
Notice how fill() recycles the same shared_ptr object by calling the reset() member function. temp.reset(p2) causes temp to replace its existing pointer p with p2 and decrement p‘s reference count. Because a copy of the original shared_ptr has already been stored in the vector (line #2), p‘s reference count is higher than 0, and therefore the reset() call doesn’t delete the original pointer allocated in line #1. When fill() exits, temp is destroyed but the shared_ptrs stored in v keep the reference count of all pointers above 0. These pointers are deleted only when v itself is destroyed.
As far as heterogeneity is concerned, the code works correctly because assigning shared_ptr
shared_ptr pf(new Foo);//fine
When pf is destroyed, it invokes Foo‘s destructor, as expected.
Pimpl Revisited
shared_ptr can eliminate the hassles of manual memory management when implementing the Pimpl idiom, which I presented here last month. In the header file containing the class’ declaration, replace the opaque pointer member with a shared_ptr (the revised code is highlighted):
//++file string.h#include #include // for shared_ptrclass String{public: String (); ~String(); //... size_t length() const; ostream & operator shared_ptr pimpl; //instead of opaque ptr };In the .cpp file, remove the destructor://++file string.cpp#include #include "Lock.h"#include "String.h"struct String::StringImpl{ vector vc; size_t len; Lock lck;};String::String(): pimpl (new String::StringImpl) {}
That’s all!