allocate_shared是如何工作的?
从std::allocate_shared in cppreference:allocate_shared是如何工作的?
构造
T
类型的对象,并使用args
作为用于T
构造函数的参数列表把它包装在std::shared_ptr
。该对象的构造方式如同使用表达式::new (pv) T(std::forward<Args>(args)...)
,其中pv
是内部void*
存储器指针,该存储器适用于容纳T
类型的对象。存储空间通常大于sizeof(T)
,以便为共享指针的控制块和对象使用一个分配。所有内存分配都使用
alloc
的副本完成,该副本必须满足Allocator
的要求。
什么让我迷惑的,想想以下用户定义的分配器,还从cppreference
template <class T>
struct Mallocator {
typedef T value_type;
Mallocator() = default;
template <class U> Mallocator(const Mallocator<U>&) {}
T* allocate(std::size_t n) { return static_cast<T*>(std::malloc(n * sizeof(T))); }
void deallocate(T* p, std::size_t) { std::free(p); }
};
template <class T, class U>
bool operator==(const Mallocator<T>&, const Mallocator<U>&) { return true; }
template <class T, class U>
bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { return false; }
由于Mallocator
只能分配的sizeof(T)
内存,怎么可能allocate_shared
分配存储其中大于sizeof(T)
中为了共享指针的控制块和T
对象使用一个分配?
这是一个两部分的答案。首先我会解释它可以做什么,然后我会解释如何。
目标是在相同的分配内分配一个控制块和一个T
的空间。这可以用一个内部模板结构来完成,这样的:
template <typename T>
struct shared_ptr_allocation {
shared_ptr_control_block cb;
typename std::aligned_storage<sizeof(T)>::type storage;
};
(假定内部shared_ptr_control_block
类型的存在我不相信该标准要求使用任何特定结构,这仅仅是一个例子。并且可能会或可能不适合实际的实现。)
因此,所有std::allocate_shared()
需要做的就是分配shared_ptr_allocation<T>
连带既控制块和的T
存储,这将放置新初始化后来。
但是,我们如何获得适合分配此结构的分配器?这个,我相信是你问题的关键,答案很简单:std::allocator_traits
。
此特征有一个rebind_alloc
模板成员,它可用于获取不同类型的分配器,该分配器由您自己的分配器构造而成。例如,allocate_shared
前几行可能看起来像:
template<class T, class Alloc, class... Args>
shared_ptr<T> allocate_shared(const Alloc& alloc, Args&&... args)
{
using control_block_allocator_t =
typename std::allocator_traits<Alloc>
::rebind_other<shared_ptr_control_block<T>>;
control_block_allocator_t control_block_allocator(alloc);
// And so on...
}
而且control_block_allocator
用于执行实际分配。
检查this sample其中我们显示当执行分配T
类型的名称,然后使用std::allocate_shared
分配一个int
。如果损坏类型名称int
是i
,我们分配的损坏类型名称是St23_Sp_counted_ptr_inplaceIi10MallocatorIiELN9__gnu_cxx12_Lock_policyE2EE
。显然,我们正在分配不同的东西!
旁白:我们可以通过前置声明一个allocator模板,它专门为一种类型,基本上留下其他专长没有定义,因此不完全证实了这一点。当我们尝试使用该分配器尝试allocate_shared
时,编译器应该抛出一个合适的结果,并且看到it does!
error: implicit instantiation of undefined template 'OnlyIntAllocator<std::_Sp_counted_ptr_inplace<int, OnlyIntAllocator<int>, __gnu_cxx::_Lock_policy::_S_atomic> >'
所以,在此实现,std::_Sp_counted_ptr_inplace
是模板结构是同时拥有控制块和对象存储。
现在,解决实际再结合如何管理在实际工作中,这里有两个关键要求,这个非常简单的分配器满足他们两个。首先,我们需要std::allocator_traits<...>::rebind_other<...>
来实际工作。从the docs cppreference:
rebind_alloc<T>
:Alloc::rebind<T>::other
如果存在的话,否则Alloc<T, Args>
如果这是Alloc
Alloc<U, Args>
由于该示例类型不具有rebind
模板构件,这个重新绑定模板简单地剥去模板参数关Mallocator<whatever>
,并用新类型替换whatever
(保留以下模板参数,如果有的话 - 在这种情况下没有)。
但是为什么要用旧的构造新的分配器,以及它是如何工作的?这是覆盖在您链接到自己的same page:
A a(b)
:构造a
这样B(a)==b
和A(b)==a
。不会抛出异常。 (注意:这意味着所有与rebind相关的分配器都会维护彼此的资源,例如内存池)
它可以分配2 * sizeof(T),但我不知道它是否真的这样做。 – immibis
@immibis T = char时可能不会奏效; – cdhowie