Class factories and shared_ptr<T>

One way to control object memory and initialization is to wrap object creation in a class factory. This is the point of the proposed make_shared<T>(..) and allocate_shared<T>(..) standard library functions.

But it is easy to roll your own simple factory function:

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

  class
factory_type
{
    /* this_type type alias */
    private:
    typedef
    factory_type
  this_type;

  // ======== Disable Copy ============

    /* disabled copy ctor */
    private:
  factory_type( this_type const &)
    ; /* no implementation */

    /* disabled copy assignment */
    private:
    void
  operator =( this_type const &)
    ; /* no implementation */

  // ======== Factory =================

    /* private default ctor */
    private:
  factory_type( )
    { }

    /* public (unfortunately) dtor */
    public:
  ~factory_type( )
    { }

    /* factory, only way to make these objects */
    public:
    static
    shared_ptr< this_type >
  make_new( )
    { return
        shared_ptr< this_type >(
          new this_type);
    }
};

  shared_ptr< factory_type >
factory_inst(
  factory_type::make_new( ));

And there you have factor_type, a tightly controlled class where all insts are created in the factory (factory_type::make_new( )) and all deletes are through shared_ptr<T>s. The private constructors means not only can we not “new” the class ourselves, we also cannot use it as a supertype. Nor can we declare a factory_type class member or stack variable.

factor_type would be improved, however, if we could ensure that only shared_ptr<T>s could access the destructor. It doesn’t work to declare the destructor private and friend class shared_ptr< this_type > because the destructor is actually called from a deleter function. However we can friend the deleter function:

  class
factory_type
{
  ... as above ...

    /* private dtor */
    private:
  ~factory_type( )
    { }

    /* the only function that calls the dtor */
    friend void boost::checked_delete( this_type *);

    /* factory, only way to make these objects */
    public:
    static
    shared_ptr< this_type >
  make_new( )
    { return
        shared_ptr< this_type >(
          new this_type);
    }
};

This works fine, but you get the following warning from msvc++9.0: the inline specifier cannot be used when a friend declaration refers to a specialization of a function template. Of course you can disable this warning. It’s not very important and likely to go away in a future version of msvc++ anyway, and it may even be corrected by an optimized non-debug compile.

The nicest solution, however, is to provide a private non-templated deleter. It doesn’t issue the above warning and it feels more portable. It’s only drawback is that it slightly increases the size of the intermediate ref-count object.

  class
factory_type
{
  ... as above ...

    /* private dtor */
    private:
  ~factory_type( )
    { }

    /* the only place where the dtor is used */
    private:
    struct
  private_deleter
    {   void 
      operator ()( this_type * p)
        { delete p; }
    };

    /* factory, only way to make these objects */
    public:
    static
    shared_ptr< this_type >
  make_new( )
    { return
        shared_ptr< this_type >(
          new this_type,
          private_deleter( ));
    }
};

static_pointer_cast<T>(..) for weak_ptr<T>s

In the last post I mentioned that the following functions are not defined for weak_ptr<T> (in Boost 1_37_0):

Boost is a very nice and carefully thought-out library, so these functions may be missing for a good reason. Or maybe they’re just considered not very important because weak pointers are not often passed around and copied on the stack. In any case, here is one way to define static_pointer_cast<T>(..) — the same pattern can be used for the other two functions.

# include <boost/assert.hpp>
# include <boost/shared_ptr.hpp>
# include <boost/weak_ptr.hpp>
# include <boost/pointer_cast.hpp>

namespace boost {

  template
   < typename TRG_T
   , typename SRC_T
   >
  weak_ptr< TRG_T >
static_pointer_cast( weak_ptr< SRC_T > const & wp_src)
  //
  // This works even if the source weak_ptr is null or
  // expired.
  //
  // No throw - this should never throw an exception.
{
  // Get a shared_ptr from the weak_ptr.
  // Use weak_ptr.lock( ) so this will not throw
  // bad_weak_ptr if the weak_ptr is expired.
    shared_ptr< SRC_T >
  sp_src(
    wp_src.lock( ));
    BOOST_ASSERT( wp_src.expired( ) == ! sp_src);

  // Cast the shared_ptr to the target type.
    shared_ptr< TRG_T >
  sp_trg(
    static_pointer_cast< TRG_T >( sp_src));

  // Auto-cast the shared_ptr< TRG_T > to the
  // weak_ptr< TRG_T > return type.
  return sp_trg;
}

} /* end namespace boost */

It would probably be more efficient to build this into the weak_ptr<T> class like it is built into the shared_ptr<T> class, but this will do.

Here is how you’d use static_pointer_cast<T>(..).

using boost::shared_ptr;
using boost::weak_ptr;
using boost::static_pointer_cast;

  class
base_type
  { public: virtual ~base_type( ) { } };

  class
derived_type
  : public base_type
  { };

  // Make a shared pointer that owns a new object.
  shared_ptr< derived_type >
sp_derived(
  new derived_type);

  // Make a weak pointer to that same object.
  weak_ptr< derived_type >
wp_derived(
  sp_derived);

  // Weak pointers upcast normally, even when they
  // are null or expired.
  weak_ptr< base_type >
wp_base(
  wp_derived);

  // Our new static_pointer_cast works with
  // weak pointers.
  weak_ptr< derived_type >
wp_derived2(
  static_pointer_cast< derived_type >( wp_base)
  );

I noticed when writing this that weak_ptr<T> does not have an automatic boolean conversion like other pointer types. You cannot write code like if ( weak_ptr_inst ).. or assert( ! weak_ptr_inst). Instead you have to use weak_ptr_inst.expired( ). This is because it would be easy to forget that a weak pointer can expire at any time, which can lead to bugs that fail only occasionally and are hard to reproduce.

  // Assume weak_ptr bool operator is defined
  // (which it isn't).
  template< typename T >
  class
weak_ptr
{
  ... other stuff ...
  // Very simple bool operator.
  operator bool( ) const { return ! expired( ); }
};

...

  weak_ptr< my_class >
weak_ptr_inst(
  shared_ptr_inst);

if ( weak_ptr_inst ) {
  ... Here you might find dangerous code that assumes
  ... weak_ptr_inst is set (not null, not expired).
  ... Code should always assume a weak_ptr can
  ... expire unless the same code is holding a
  ... shared_ptr to the same target object.
}

if ( ! weak_ptr_inst.expired( ) ) {
  ... This is the same as above, but it might be safer
  ... because the explicit call to expired( ) reminds
  ... us we are working with a weak_ptr, which
  ... doesn't fit into more common pointer patterns.
}

Using static_pointer_cast<T>(..) and dynamic_pointer_cast<T>(..)

This demonstrates how to use static_pointer_cast<T>(..) and dynamic_pointer_cast<T>(..). It is like the previous post which shows how to use const_pointer_cast<T>(..).

First the headers and some class declarations:

# include <boost/assert.hpp>
# include <boost/pointer_cast.hpp>
# include <boost/shared_ptr.hpp>
using boost::shared_ptr;
using boost::static_pointer_cast;
using boost::dynamic_pointer_cast;

  class
base_type
  { public: virtual ~base_type( ) { } };

  class
derived_type
  : public base_type
  { };

  class
derived_type_other
  : public base_type
  { };

Notice base_type has a virtual destructor, which makes it a polymorphic class. A class has to have at least one virtual method if you want to use it with dynamic_pointer_cast<T>(..). Otherwise the compiler complains loudly.

Remember that, in general, declaring the destructors virtual is often not necessary if a class is being managed by shared_ptr<T>. If you always immediately attach a new target object to a shared_ptr< MOST_DERIVED_CLASS > after creation, then you don’t have to worry if shared_ptr< BASE_CLASS > later deletes that target object. The most-derived destructor is bound to the shared_ptr<T> during first attach and is passed along to subsequent shared and weak pointers.

Finally, here is the code showing static_pointer_cast<T>(..) and dynamic_pointer_cast<T>(..).

  // Make a shared_ptr to a new object.
  shared_ptr< derived_type >
sp_derived(
  new derived_type);

  // The shared_ptr knows how to upcast its
  // inner pointer.
  shared_ptr< base_type >
sp_base(
  sp_derived);

  // You can static-downcast the inner pointer.
  shared_ptr< derived_type >
sp_derived2(
  static_pointer_cast< derived_type >( sp_base));

  // You can dynamic-downcast the inner pointer
  // as long as the inner type is polymorphic.
  shared_ptr< derived_type >
sp_derived3(
  dynamic_pointer_cast< derived_type >( sp_base));
  BOOST_ASSERT( sp_derived3);

  // You can try to dynamic-downcast to the wrong type.
  // You will end up with a null shared_ptr because
  // the inner dynamic-downcast returns a zero pointer.
  // This does not throw std::bad_cast because it casts
  // pointers and not refs.
  shared_ptr< derived_type_other >
sp_derived_other(
  dynamic_pointer_cast< derived_type_other >( sp_base));
  BOOST_ASSERT( ! sp_derived_other);

Like const_pointer_cast<T>(..), static_pointer_cast<T>(..) and dynamic_pointer_cast<T>(..) also work with intrusive_ptr<T>s and raw pointers. But they don’t work with weak_ptr<T>, at least not in Boost 1_37_0 (I suspect it’s an oversight). They also do not work with shared_array<T>, which makes sense since array “pointers” should always be to the most-derived type, and of course they don’t work with the single-ownership smart pointers that don’t support normal copy semantics.

There is also a reinterpret_pointer_cast<T>(..) function, but it only works with raw pointers. Reinterpret cast is usually only used at a very low abstraction level, while smart pointers are highly abstracted. If you think you need a reinterpret cast with a smart pointer you are probably doing something wrong.

Next Page →