Search code examples
c++memory-managementboostshared-ptrmake-shared

Where does boost::make_shared size of memory allocation for underlying object and reference counting object?


I am trying to understand how boost::make_shared does the memory allocation for the object managed by a boost::shared_ptr and the reference-counting object (the shared_ptr uses) together.

The make_shared function begins execution here:

template< class T, class A1, class A2, class A3 >
typename boost::detail::sp_if_not_array< T >::type make_shared( A1 && a1, A2 && a2, A3 && a3 )
{
    //Seems to create the smart_ptr for the object
    boost::shared_ptr< T > pt( static_cast< T* >( 0 ), BOOST_SP_MSD( T ) );

    //Not sure?
    boost::detail::sp_ms_deleter< T > * pd = static_cast<boost::detail::sp_ms_deleter< T > *>( pt._internal_get_untyped_deleter() );

    //Calculates the address at which the bulk-allocation begins
    void * pv = pd->address();

    //Allocates the memory at address pv?
    ::new( pv ) T(
        boost::detail::sp_forward<A1>( a1 ), 
        boost::detail::sp_forward<A2>( a2 ), 
        boost::detail::sp_forward<A3>( a3 )
        );

    //Not sure
    pd->set_initialized();

    //Not sure
    T * pt2 = static_cast< T* >( pv );

    //Not sure
    boost::detail::sp_enable_shared_from_this( &pt, pt2, pt2 );
    return boost::shared_ptr< T >( pt, pt2 );
}

Would somebody be able to help explain the remaining lines?

I am trying to identify where the size of the bulk memory allocation (the object being pointed to and the shared_ptr reference counting object) is determined?

What's throwing me is that the call to address() doesn't seem to consider the size of the T object when allocating the address to allocate at.

(I really don't get what the three AX parameters are which enter the method and are passed to the placement new() call)


Solution

  • Let's take it line by line

      boost::shared_ptr< T > pt( static_cast< T* >( 0 ), BOOST_SP_MSD( T ) );
    

    This creates the shared pointer, but the key here is the deleter. The BOOST_SP_MSD(T) is a macro which resolves to a deleter with enough extra space for your data. So the shared pointer reference count block also includes the space for the deleter, which now includes the space for your T

        //Not sure?
        boost::detail::sp_ms_deleter< T > * pd = static_cast<boost::detail::sp_ms_deleter< T > *>( pt._internal_get_untyped_deleter() );
    

    This gets the deleter address from the shared pointer. This will point to the deleter created above.

        //Calculates the address at which the bulk-allocation begins
        void * pv = pd->address();
    

    This returns the starting address of for the type T, which is currently uninitialized and is part of the deleter

        //Allocates the memory at address pv?
        ::new( pv ) T(
            boost::detail::sp_forward<A1>( a1 ), 
            boost::detail::sp_forward<A2>( a2 ), 
            boost::detail::sp_forward<A3>( a3 )
            );
    

    This is a placement-new. This constructs your T at the address provided in pv. It's passing 3 arguments because this is the 3-arg version of make_shared.

        //Not sure
        pd->set_initialized();
    

    This is an internal flag in the deleter, which lets it know that the T has been constructed (so that when the deleter's operator() is called, it will destroy it)

        //Not sure
        T * pt2 = static_cast< T* >( pv );
    

    This casts the void* above into the T*. Honestly, I'm not sure why they just didn't retain the result of the placement new.

        //Not sure
        boost::detail::sp_enable_shared_from_this( &pt, pt2, pt2 );
    

    This is necessary to provide the enable_shared_from_this functionality. This internal function sets up the underlying mechanism for enable_shared_from_this. It's normally called when you place something into the shared_ptr

        return boost::shared_ptr< T >( pt, pt2 );
    

    This actually creates a new boost::shared_ptr that uses the same reference counted region as the pt, but its get() and related methods will return pt2, which is the pointer to T, which is stored in the deleter.