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 theT
object. ... All memory allocation is done using a copy ofalloc
, 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?
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 rebind
ed 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.