2017-06-15 215 views
2

std::allocate_shared in cppreferenceallocate_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对象使用一个分配?

+0

它可以分配2 * sizeof(T),但我不知道它是否真的这样做。 – immibis

+0

@immibis T = char时可能不会奏效; – cdhowie

回答

3

这是一个两部分的答案。首先我会解释它可以做什么,然后我会解释如何。

目标是在相同的分配内分配一个控制块和一个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。如果损坏类型名称inti,我们分配的损坏类型名称是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>如果这是AllocAlloc<U, Args>

由于该示例类型不具有rebind模板构件,这个重新绑定模板简单地剥去模板参数关Mallocator<whatever>,并用新类型替换whatever(保留以下模板参数,如果有的话 - 在这种情况下没有)。

但是为什么要用旧的构造新的分配器,以及它是如何工作的?这是覆盖在您链接到自己的same page

A a(b):构造a这样B(a)==bA(b)==a。不会抛出异常。 (注意:这意味着所有与rebind相关的分配器都会维护彼此的资源,例如内存池)

+0

非常感谢!我知道了。所以如果我想用T实现一个池,我需要预先分配一组sizeof(internal_type为)的内存组,因为我不知道它的内部类型是什么,也许我可以使用sizeof(T )+32? – alpha

+0

@alpha如果你想成为邪恶的,你可以做一些[这样](http://coliru.stacked-crooked.com/a/f7fefd18c896f50b)来检测大小。 – cdhowie

+0

我明白了。我可以在程序init函数中调用一次dummy allocate_shared来获取大小,然后分配一组内存。 – alpha