Notes on shared_ptr< T >

The C++ shared_ptr< T > is a strong shared-ownership pointer that keeps the target object (the referent) alive as long as it points to it. The last shared_ptr< T > to drop an object also deletes it.

The C++ shared_ptr< T > uses reference counting to know how many other shared_ptr< T >s are pointing at the object. The reference count is not stored in the target object. Instead the shared_ptr< T > creates an intermediate object which holds the reference count and a pointer to the target.

This intermediate object has to be allocated, so when you alloc a new object and give it to a shared_ptr< T >, you are also allocating a new intermediate object. Creating 100 new shared objects also creates 100 new intermediate objects.

Many shared_ptr< T >s and weak_ptr< T >s can point to one intermediate object, but only one intermediate object should point to the target object. The pointer in the intermediate object has sole ownership of the target object and never lets it go until it deletes it. In this way the pointer in the intermediate object is a lot like a scoped_ptr< T >.

There should never be more than one intermediate object for any given target. This is important to remember because it is easy to get wrong. All you have to do is create a shared_ptr< T > twice from the same raw pointer.

# include <boost/shared_ptr.hpp>
using boost::shared_ptr;

class my_class1 { };

// Creates a my_class1 object.
my_class * p_a = new my_class1;

// Creates (allocs) a new intermediate sharing and
// ref-counting object. This object sits between
// sp1_a and p_a.
shared_ptr< my_class1 > sp1_a( p_a);

// Copy the shared_ptr to another shared_ptr.
// Uses the same intermediate object in both shared_ptrs.
shared_ptr< my_class1 > sp1_b( sp1_a);

// Double-delete bug:
// Creating a shared_ptr from a raw pointer causes
// another intermediate object to be created. This
// is only OK the first time, and illegal now since
// an intermediate object has already been created
// for p_a.
// Even though this is illegal it still compiles and
// runs. But p_a gets deleted twice since it is now
// "owned" by two intermediate objects.
shared_ptr< my_class1 > sp1_c( p_a); /* BUG! */

After you create the first shared_ptr< T > for an object, you should create all future shared and weak pointers from existing shared/weak pointers.

But there is a way around this. The base class boost::enable_shared_from_this< T > keeps a private weak_ptr< T > that points back to itself. It then uses that weak_ptr< T > to make shared_ptr< T >s through the method shared_from_this( ).

# include <boost/shared_ptr.hpp>
# include <boost/enable_shared_from_this.hpp>
using boost::shared_ptr;
using boost::enable_shared_from_this;

class my_class2
: public enable_shared_from_this< my_class2 >
{ };

// These are all good.
my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_a( p_2);
shared_ptr< my_class2 > sp2_b( sp2_a);
shared_ptr< my_class2 > sp2_c = p_2->shared_from_this( );

But boost::enable_shared_from_this< T > doesn’t solve all problems. It doesn’t stop double deletes.

my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_a( p_2);

// Illegal even with enable_shared_from_this<T> base.
// The object will be deleted twice.
shared_ptr< my_class2 > sp2_b( p_2);

In this case the boost implementation (1_37_0) calls the _internal_assign(..) method from weak_ptr.hpp.

// from weak_ptr.hpp in boost 1_37_0
void _internal_assign(T * px2, boost::detail::shared_count const & pn2)
{
    px = px2;
    pn = pn2;
}

I think this code could use an assert, which would detect a situation that eventually leads to a double delete. The code would then look like this:

// from weak_ptr.hpp in boost 1_37_0 with added assert
void _internal_assign(T * px2, boost::detail::shared_count const & pn2)
{
    // This is only used to initialize the weak pointer in the
    // class enable_shared_from_this<T>. If the following
    // assert fails then the referent has already been given
    // over to a shared_ptr.
    BOOST_ASSERT( (px == 0) && (pn.use_count() == 0));
    px = px2;
    pn = pn2;
}

If you have a raw pointer of a enable_shared_from_this< T > subtype you might think you can just always call ->shared_from_this( ) to get a shared pointer, but that doesn’t work either. You have to always assign the raw pointer to a shared_ptr< T > first, before you can call ->shared_from_this( ).

// Throws boost::bad_weak_ptr.
// Cannot call ->shared_from_this( ) until after the first
// time it is assigned to a shared_ptr<..>.
my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_e( p_2->shared_from_this( ));

The shared_ptr< T > raw-pointer constructor could easily be re-written to avoid these double-delete problems for subtypes of enable_shared_from_this< T > by figuring out if the internal weak_ptr< T > is initialized or not before deciding what to do. You can even do it yourself with something like this.

  template< typename T >
  shared_ptr< T >
get_shared_ptr_for( T * p_obj)
{
  try {
    return p_obj->shared_from_this( );
  }
  catch ( boost::bad_weak_ptr ) {
    return shared_ptr< T >( p_obj);
  }
}

...

my_class2 * p_2 = new my_class2;
shared_ptr< my_class2 > sp2_f( get_shared_ptr_for( p_2));
shared_ptr< my_class2 > sp2_g( get_shared_ptr_for( p_2));
BOOST_ASSERT( sp2_f == sp2_g);

Changing the shared_ptr< T >::shared_ptr( T *) constructor to do something similar might be a good idea, but it would not work for classes that did not derive from enable_shared_from_this< T >. The inconsistency between the two types of classes, those with and those without a enable_shared_from_this< T > supertype, would also cause confusion, especially with generic code.

It would be more consistent with intrusive_ptr< T > though, which doesn’t use intermediate sharing/ref-count objects. Or maybe it’s more accurate to say the intermediate object is intrusively part of the target object, so you can never have more than one for any given target. You don’t have to worry about double-deletes with intrusive_ptr< T >s.

Smart Pointers — Intro

Smart pointers are coming. C++ already provides auto_ptr< T > as part of the standard library, and C++0x will be adding shared_ptr< T > and weak_ptr< T >, which are now part of TR1. Boost provides a couple other smart pointers.

Smart pointers help manage memory using ownership and reference counting. When using them you have to watch for circular dependencies, which you don’t have to worry about in garbage-collecting languages like Lisp, Python, Ruby, and Java. Since C++ doesn’t come with a garbage collector you have to manage memory yourself, and smart pointers are a tool you can use. A memory management scheme is not the same thing as automatic garbage collection.

I’m going to be writing a few short posts about smart pointers in the next few days. This is just an introduction and some links.

← Previous Page