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 theTobject. ... 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 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.
It doesn't matter. The type must only satisfy the allocator requirements ([util.smartptr.shared.create]/2, also in C++11).
One of the requirements is that for every (cv-unqualified) object type U, Alloc::rebind<U>::other yields the corresponding allocator type for a value_type of U (or if this is not implemented directly in the allocator type that allocator_traits can provide a default implementation by replacing the (first) template argument of the class template that Alloc is a specialization of.)
In this way, no matter for what value_type your passed allocator is, std::allocate_shared can just obtain an allocator to the (unspecified) type it requires for combined storage by rebinding.
For example, with
struct MyType {};
int main() {
std::cout << "MyType size is " << sizeof(MyType) << "\n";
allocator<MyType> a;
auto x = std::allocate_shared<MyType>(a);
}
where allocator is a minimal allocator implementation printing details about every allocation call via typeid, I get for current GCC with libstdc++:
MyType size is 1
Allocating for 1 objects of type St23_Sp_counted_ptr_inplaceI6MyType9allocatorIS0_ELN9__gnu_cxx12_Lock_policyE2EE
with size 24 each.
and with current Clang and libc++:
MyType size is 1
Allocating for 1 objects of type NSt3__120__shared_ptr_emplaceI6MyType9allocatorIS1_EEE
with size 32 each.
https://godbolt.org/z/bPqeYTP7Y
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With