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_shared
的 T
混淆。因此,例如,通过 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-不合格的)对象类型 U
,Alloc::rebind<U>::other
为 U
的 value_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.
实际使用的类型取决于实现。通过 Allocator requirements and with the help of std::allocator_traits
traits class 模板,任何分配器都可以通过 std::allocator_traits<A>::rebind_alloc<T>
机制 rebind
ed 到另一种类型。
假设你有一个分配器
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.
来自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.
然后使用什么类型来分配上述存储?换句话说,分配器要求之一 Alloc::value_type
应该是什么?
what should be Alloc::value_type
分配器的要求是 Alloc::value_type
与分配器要求中的 T
相同 - 不要与 allocate_shared
的 T
混淆。因此,例如,通过 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-不合格的)对象类型 U
,Alloc::rebind<U>::other
为 U
的 value_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.
实际使用的类型取决于实现。通过 Allocator requirements and with the help of std::allocator_traits
traits class 模板,任何分配器都可以通过 std::allocator_traits<A>::rebind_alloc<T>
机制 rebind
ed 到另一种类型。
假设你有一个分配器
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.