Home > Articles > Programming > C/C++

C++ Reference Guide

Hosted by

Toggle Open Guide Table of ContentsGuide Contents

Close Table of ContentsGuide Contents

Close Table of Contents

The <code>shared_ptr</code> Class

Last updated Jan 1, 2003.

If you’re looking for more up-to-date information on this topic, please visit our C/C++ Programming article, podcast, and store pages.

C++98 has only one smart pointer class, namely auto_ptr. The Library Extensions Technical Report 1 (known as TR1) introduces new smart pointer classes that rectify many of auto_ptr's problems and limitations. Here, I present the most widely-used smart pointer class of TR1, namely shared_ptr, and contrast it with good old auto_ptr.

auto_ptr's Limitations

auto_ptr employs the "exclusive ownership" model. This means that you can't bind more than one auto_ptr object to the same resource. To ensure exclusive ownership, the copy and assign operations of auto_ptr cause the source object to hand down the owned resource to the target object. Let's see an example:

void f()
{
 std::auto_ptr<Foo> pfoo1(new Foo);
 pfoo1->func(); //use pfoo1 as if it were the Foo object
 //copying changes the source and the target
 std::auto_ptr<Foo> pfoo2(pfoo1); //now pf002 owns the pointer to Foo
                 //pfoo1 becomes null
 pfoo2->func();
} //pfoo's destructor deletes the Foo pointer

There are at least two problems with this ownership model.

  • Counter-intuitive copy semantics. Generally, when you copy a source to a target, you don't expect the source to change its state. When you assign a string s1 to another string s2, you surely don't expect s1 to change. It's worth mentioning that copying occurs more frequently than what may seem at first: returning and passing objects by value are instances of copying, too.
  • Incompatibility with STL. You can't use auto_ptr as elements of STL containers. The reason is that containers may occasionally move elements around (say, during reallocation or when you assign containers). The elements must therefore be copy-constructible and assignable; auto_ptr is neither of these.

Why was this design of auto_ptr chosen in the first place? Let's look at the previous code listing again to see why. If multiple auto_ptrs were allowed to hold the same pointer, the results would be disastrous. When f() exits, the destructors of the two auto_ptrs would delete the same pointer twice:

void f()
{
 std::auto_ptr<Foo> pfoo1(new Foo);
 pfoo1->func();
 //imagine that copying doesn't change the source
 std::auto_ptr<Foo> pfoo2(pfoo1);
// pfoo1and pfoo2 own the same object

 pfoo2->func();
} //both pfoo1 and pffo2's destructors delete the Foo pointer

Now let's see how shared_ptr solves this problem neatly.

Enter shared_ptrs

As with many other TR1 components, the first implementation of shared_ptr was available on Boost. The input from a vast number of users and gurus has enabled its designers to fine-tune the interface and performance of this class. Only then was shared_ptr incorporated into TR1.

The result is a robust, flexible and rather efficient smart pointer class that rectifies the ailments of auto_ptr, while extending the potential usage of shared_ptr to diverse applications domains and frameworks, such as COM.

The interface of shared_ptr is pretty reminiscent of auto_ptr's, which is just as well; the learning curve isn't steep, and the migration of auto_ptr-based code to shared_ptr isn't difficult, either.

However, there is a crucial difference between the two. Unlike auto_ptr, shared_ptr uses reference counting. Consequently, it's possible to have multiple shared_ptrs that point to the same resource at once. These objects use a reference counter. When a shared_ptr's destructor is invoked, the reference counter is decremented. Only when the reference counter reaches 0 does the shared_ptr destructor release the resource. This design has two advantages: you can safely store shared_ptr objects as elements of STL containers, and you can safely create multiple shared_ptrs that point to the same resource. In multithreaded apps, this enables you, for instance, to create a shared_ptr object per thread, and execute different member functions of the same resource object concurrently:

Foo * p = new Foo;

{//thread 1; executes concurrently with thread 2
 std::tr1::shared_ptr<Foo> pfoo1(p);
 pfoo1->func();
 return 0;
}
{//thread 2; executes concurrently with thread 1
 std::tr1::shared_ptr<Foo> pfoo2(p);
 pfoo1->another_func();
 return 0;
}

It doesn't matter which thread finishes first; in any occasion, shared_ptr "just works" -- the last instance thereof will delete p;

As all smart pointer classes, shared_ptr overloads the -> and * operators so you normally don't need to access its raw pointer directly. However, if you need to access the pointer directly, call the get() member function:

Foo * p_alias = std::tr1::pfoo1.get();
assert(p_alias==p);

Construction

The shared_ptr constructor takes two optional arguments. The first is obviously a pointer to the resource object, as I've shown before. Notice that the constructor is templated on the argument's type. This allows you to do the following:

std::tr1:shared_ptr <void> pf(new Foo);

When pf is destroyed, it invokes Foo's destructor, as expected.

The second optional argument is even more interesting. It specifies a user-defined deleter, which is a function (in its wider sense, e.g., a function object, a pointer to a freestanding function etc.) that the shared_ptr destructor executes when the reference count reaches zero. By default, shared_ptr's destructor simply calls delete to destroy its resource. However, when you provide a custom deleter, shared_ptr's destructor invokes that deleter instead. This is useful when the resource in question isn't a raw pointer to dynamic memory, or when the dynamic memory belongs to a different heap (and therefore shouldn't be deleted, say a pointer to an object allocated in a different DLL):

//API functions
DeviceContext * get_dc();
int release_dc();
void draw()
 //provide a user-defined deleter
 std::tr1::shared_ptr<DeviceContext> dc(get_dc(), release_dc);
 //...use dc
 //instead of deleting the dc pointer, the release_dc() API function
 //will be called when dc is destroyed.
}

It's also possible to construct a null shared_ptr, possibly binding a valid pointer to it later:

std::tr1::shared_ptr<Bar> pb;

use_count() and unique()

The use_count() member function returns the number of clients (i.e., shared_ptr objects) currently sharing the same resource. The common

use_count() == 1

query has its own name: unique():

std::tr1::shared_ptr<DeviceContext> dc(get_dc(), release_dc);
if(dc.unique())
{
 //...resource isn't shared with others
}

The shared_ptr class also defines a bool conversion operator, enabling you to use it in Boolean expressions.

The relative operators ==, != and < are also defined. They do not compare raw pointers but rather use the std::less predicate to perform the comparison.

In the next installment, I will show how to use shared_ptr to simulate a heterogeneous container and discuss the related class weak_ptr, which is also part of TR1.