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.