std::allocate_shared使用什么类型分配内存?

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

来自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.

然后使用什么类型来分配上述存储?换句话说,分配器要求之一 Alloc::value_type 应该是什么?

what should be Alloc::value_type

分配器的要求是 Alloc::value_type 与分配器要求中的 T 相同 - 不要与 allocate_sharedT 混淆。因此,例如,通过 Alloc::​pointer 进行间接访问必须产生类型为 Alloc::value_type&.

的值

Alloc::value_type 不需要满足 std::allocate_shared 的要求,只要符合分配器要求即可。

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

std::allocate_shared 将使用 alloc 的副本反弹到未指定的 value_type.

没关系。该类型必须仅满足分配器要求([util.smartptr.shared.create]/2,也在 C++11 中)。

其中一个要求是,对于每个(cv-不合格的)对象类型 UAlloc::rebind<U>::otherUvalue_type 产生相应的分配器类型(或者如果这没有在分配器类型中直接实现,那么 allocator_­traits 可以通过替换 Alloc 特化的 class 模板的(第一个)模板参数来提供默认实现。)

这样,无论value_type你传递的分配器是什么,std::allocate_shared都可以通过重新绑定获得组合存储所需的(未指定)类型的分配器。

例如,

struct MyType {};

int main() {
    std::cout << "MyType size is " << sizeof(MyType) << "\n";
    allocator<MyType> a;
    auto x = std::allocate_shared<MyType>(a);
}

其中 allocator 是一个最小的分配器实现,通过 typeid 打印有关每个分配调用的详细信息,我使用 libstdc++ 获取当前 GCC:

MyType size is 1
Allocating for 1 objects of type St23_Sp_counted_ptr_inplaceI6MyType9allocatorIS0_ELN9__gnu_cxx12_Lock_policyE2EE
    with size 24 each.

以及当前的 Clang 和 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

实际使用的类型取决于实现。通过 Allocator requirements and with the help of std::allocator_traits traits class 模板,任何分配器都可以通过 std::allocator_traits<A>::rebind_alloc<T> 机制 rebinded 到另一种类型。

假设你有一个分配器

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

如果你写:

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

这并不意味着 MyAlloc<T> 将用于执行分配。如果在内部我们需要分配另一种类型的对象 S,我们可以通过

获得合适的分配器
std::allocator_traits<MyAlloc<T>>::rebind_alloc<S>

定义为如果MyAlloc本身不提供rebind<U>::other成员类型别名,则使用默认实现,即returnsMyAlloc<S>。分配器对象是通过适当的 MyAlloc<S> 的转换构造函数(参见 Allocator 要求)从传递给 std::allocate_shared()(此处为 MyAlloc<T>{})的对象构造的.

让我们来看看一些特殊的 implementation - libstdc++。对于上面的行,实际分配由

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

这是合理的:std::allocate_shared() 为同时包含 T 和控制块的对象分配内存。 _Sp_counted_ptr_inplace<T, ...>就是这样一个对象。它在 _M_storage 数据成员中包含 T 内部:

__gnu_cxx::__aligned_buffer<T> _M_storage;

许多其他地方使用相同的机制。例如,std::list<T, Alloc> 使用它来获得一个分配器,然后用于分配列表节点,除了 T 之外,它还保存指向其邻居的指针。

一个有趣的相关问题是为什么分配器不是模板模板参数,这似乎是一个自然的选择。讨论 here.