Specializing make_shared< T > and allocate_shared< T >

In my last post I talked about factory_type, a class that supplies a factory function make_new( ) as a static method.

(In retrospect, I suppose factory_type isn’t such a good name since it sounds like the instances are factories. Better names might be factory_made_object_type or gizmo_type.)

Anyway, the code looked like this:

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

  class
factory_type
{
    /* this_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( )
    { }

    /* 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( ));
    }
};

Now there’s been some discussion (see here and here) about defining standard factory template functions called make_shared<T>(..) and allocate_shared<T,A>( A const &, ..) with signatures like this:

namespace std {

  template< typename T >
  shared_ptr< T >
make_shared( )
  ;

  template< typename T, typename ALLOC_T >
  shared_ptr< T >
allocate_shared( ALLOC_T const & allocator_inst)
  ;

  template< typename T, typename ... ARG_Ts >
  shared_ptr< T >
make_shared( ARG_Ts && ... args)
  ;

  template< typename T, typename ALLOC_T, typename ... ARG_Ts >
  shared_ptr< T >
allocate_shared( ALLOC_T const & alloc_inst, ARG_Ts && ... args)
  ;

} /* end namespace std */

It is easy to specialize make_shared< factory_type >( ) to use our class’s private factory.

namespace std {

  // specialize make_shared<T>( )
  template< >
  shared_ptr< factory_type >
make_shared< factory_type >( )
  { return factory_type::make_new( ); }

} /* end namespace std */

Let’s try some more complicated factories. Assume factory_type has some additional constructors and factories.

  class
factory_type
{
  ... as declared above ...

  // ======== Constructors =================

    /* private default ctor */
    private:
  factory_type( )
    ;

    /* private ctor */
    private:
  factory_type( float, void *, char = 'A')
    ;

    /* private ctor */
    private:
  factory_type( double, long, std::string const &)
    ;

  // ======== Factories =================

    public:
    static
    shared_ptr< this_type >
  make_new( )
    { return
        shared_ptr< this_type >(
          new this_type,
          private_deleter( ));
    }

    public:
	template< typename ... ARG_Ts >
    static
    shared_ptr< this_type >
  make_new( ARG_Ts && ... args)
    { return
        shared_ptr< this_type >(
          new this_type( std::forward< ARG_Ts >( args) ...),
          private_deleter( ));
    }

  // ======== Factories with allocators =======

    public:
	template< typename ALLOC_T >
    static
    shared_ptr< this_type >
  allocate_new( ALLOC_T const & alloc_inst)
    { return
        shared_ptr< this_type >(
          new this_type,
          private_deleter( ),
		  alloc_inst);
    }

    public:
	template< typename ALLOC_T, typename ... ARG_Ts >
    static
    shared_ptr< this_type >
  allocate_new( ALLOC_T const & alloc_inst, ARG_Ts && ... args)
    { return
        shared_ptr< this_type >(
          new this_type( std::forward< ARG_Ts >( args) ...),
          private_deleter( ),
		  alloc_inst);
    }
};

We’ll find it much harder to specialize make_shared<T>(..) and allocate_shared<T,A>( A const &, ..) to use these class-supplied factories that take parameters because you cannot partially specialize a templated function like you can a templated struct. Forgetting that, you might try:

namespace std {

  // Illegal partial specialization of template function.
  // Does not compile!!
  template< typename A_T >
  shared_ptr< factory_type >
allocate_shared< factory_type, A_T >( A_T const & alloc_i)
  { return factory_type::allocate_new( alloc_i); }

} /* end namespace std */

And the compiler will choke and complain. You could try to fully specialize the template function since partial specialization is illegal, but that means you have to know the type of the allocator beforehand. And allocators tend to come in many types. Another approach would be to define a separate overloaded (not specialized) template function called allocate_new, but typename T would have to be the first template parameter, and the first thing you want to do is specialize that as factory_type. So that doesn’t improve things in this case.

We could make this work if the first template parameter was used in the function’s argument list. If instead of allocate_shared< factory_type >( alloc_inst) the idiom was allocate_shared_2( factory_type::tag( ), alloc_inst) then we could define another template function around ALLOC_T and we would not have to specialize. But in this case that’s not a very attractive option.

Another probably better option is to define allocate_shared<T,A>(..) as a simple call to something like shared_ptr_maker<T>::allocate(..). Then we could specialize the template class shared_ptr_maker<T> instead of the function.

And finally, I’m not sure, but these examples may be an abuse of make_shared and allocate_shared. These were originally proposed as a way to hide use of the new operator since shared_ptr also hides delete. But looking at Peter Dimov’s code suggests the purpose of these functions may now be to implement an allocation strategy, where the intermediate object (aka the “control block”) and the target object are allocated as one chunk of memory instead of as two. If that’s so, it’s unlikely you’d provide specializations or overloads for these functions.

Comments

Leave a Reply