Search code examples
c++c++11shared-ptrdynamic-memory-allocationallocator

What type is used by std::allocate_shared to allocate memory?


From https://en.cppreference.com/w/cpp/memory/shared_ptr/allocate_shared:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

The storage is typically larger than sizeof(T) in order to use one allocation for both the control block of the shared pointer and the T object. ... All memory allocation is done using a copy of alloc, which must satisfy the Allocator requirements.

What type is then used to allocate the aforementioned storage? In other words, what should be Alloc::value_type, one of the Allocator requirements?


Solution

  • The actual type used depends on the implementation. By Allocator requirements and with the help of std::allocator_traits traits class template, any allocator can be rebinded to another type via std::allocator_traits<A>::rebind_alloc<T> mechanism.

    Suppose you have an allocator

    template<class T>
    class MyAlloc { ... };
    

    If you write:

    std::allocate_shared<T>(MyAlloc<T>{});
    

    it doesn't mean that MyAlloc<T> will be used to perform allocations. If internally we need to allocate an object of another type S, we can get an appropriate allocator via

    std::allocator_traits<MyAlloc<T>>::rebind_alloc<S>
    

    It is defined such that if MyAlloc itself doesn't provide rebind<U>::other member type alias, the default implementation is used, which returns MyAlloc<S>. An allocator object is constructed from that passed to std::allocate_shared() (here, MyAlloc<T>{}) by an appropriate MyAlloc<S>'s converting constuctor (see Allocator requirements).

    Let's take a look at some particular implementation - libstdc++. For the line above, the actual allocation is performed by

    MyAlloc<std::_Sp_counted_ptr_inplace<T, Alloc<T>, (__gnu_cxx::_Lock_policy)2>
    

    which is reasonable: std::allocate_shared() allocates memory for an object that contains both T and a control block. _Sp_counted_ptr_inplace<T, ...> is such an object. It holds T inside itself in the _M_storage data member:

    __gnu_cxx::__aligned_buffer<T> _M_storage;
    

    The same mechanism is used in many other places. For example, std::list<T, Alloc> employs it to obtain an allocator that is then used to allocate list nodes, which in addition to T hold pointers to their neighbours.

    An interesting related question is why allocator is not a template template parameter, which might seem a natural choice. It is discussed here.