allocate_shared如何工作?

时间:2017-06-15 01:59:05

标签: c++ c++11

来自std::allocate_shared in cppreference

  

构造一个T类型的对象,并使用std::shared_ptr作为args构造函数的参数列表将其包装在T中。该对象的构造就像表达式::new (pv) T(std::forward<Args>(args)...)一样,其中pv是指向存储的内部void*指针,适合存放T类型的对象。存储通常大于sizeof(T),以便对共享指针的控制块和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对象?

1 个答案:

答案 0 :(得分: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。如果int的错位类型名称为i,则我们分配的错位类型名称为St23_Sp_counted_ptr_inplaceIi10MallocatorIiELN9__gnu_cxx12_Lock_policyE2EE。显然,我们正在分配不同的东西!

除此之外:我们可以通过向前声明一个分配器模板并将其专门化为一种类型来确认这一点,基本上使其他特化没有定义,因此不完整。当我们尝试使用该分配器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):构建aB(a)==bA(b)==a。不抛出异常。 (注意:这意味着由rebind相关的所有分配器都保持彼此的资源,例如内存池)