如何使用符合STL标准的分配器进行异构内存分配

12

我正在尝试实现一个类,它在内存中由某种任意类型的数组跟随:

template<class T>
class Buf
{
    size_t n;
    int refs;
    explicit Buf(size_t n) : n(n) { }
    // other declarations are here as appropriate

    // Followed in memory by:
    // T items[n];
};

这很容易使用operator new实现:
template<class T>
Buf<T> *make_buf(size_t n)
{
    // Assume the caller will take care of constructing the array elements
    return new(operator new(sizeof(Buf<T>) + sizeof(T) * n)) Buf<T>(n);
}

template<class T>
void free_buf(Buf<T> *p)
{
    // Assume the caller has taken care of destroying the array elements
    p->~Buf<T>();
    return operator delete(p);
}

template<class T>
T *get_buf_array(Buf<T> *p)
{
    return reinterpret_cast<T *>(reinterpret_cast<char *>(p) + sizeof(Buf<T>));
}

现在,我该如何使用符合标准的SomeAllocator分配器来实现呢?

SomeAllocator::rebind<char>::other::allocate保证返回适合任何类型对象的内存空间吗?如果是,那么只使用某个字符类型的分配器就足够安全了吗?如果不是,是否有其他选择,或者说使用分配器根本无法完成这个任务?(最坏的情况下,我可以将指针强制转换为uintptr_t并手动对齐,但我想知道是否有更好的方法。)


2
你可以随时请求更多的内存,然后使用std::align... - Kerrek SB
@KerrekSB:哇,不错。我不知道那个函数存在... - user541686
@KerrekSB:我猜问题仍然存在:什么是最好的方法?是否有必要调用那个函数?我应该使用char的分配器还是T的分配器等。 - user541686
1
Buf<T>的分配器按照该类型的倍数进行分配,因此我认为这并没有什么用处。当然,您可以进行一些大小计算,但至少您仍需要以某种方式对第一个数组元素的地址进行对齐。 - Kerrek SB
如果您使用GCC或其他具有非标准扩展的编译器并且不关心可移植性,那么声明一个1元素的数组甚至是0元素的数组可能会更方便-然后您就知道Buf和元素的对齐方式已经处理好了-否则您需要获取它们的最大对齐方式。对于分配器...您可以使用rebind<Aligned_Allocator<std::alignment_of<Buf<T>>>,其中AlignedAllocator被专门化,因此<1>需要对齐1(例如包含一个int8_t),<2>需要对齐2等等。 - Tony Delroy
显示剩余3条评论
4个回答

1
我认为解决方案可能是创建一个概念性的早期数组。
+-----------+
|Buf<T>     |
+-------+---+---+-------+-------+
|T[0]   | T[1]  |T[2]   |  T[3]...
+-------+-------+-------+-------+

With the non-overlapping T[2], T[3], ... being the required array of T.

template<class T>
class Buf
{
    size_t n;
    int refs;
    explicit Buf(size_t n) : n(n) { }
    // other declarations are here as appropriate

    // Followed in memory by:
    // T items[n];
};

破损元素的数量将是:

const size_t lead = ( sizeof(Buf<T>) + sizeof(T) - 1) / sizeof(T);

最终,i的原始内存可以通过以下方式访问:

(reinterpret_cast<T*>( this ) )[ i + lead ];

0

通过将分配器重新绑定到std::aligned_storage的特化来限制对齐。

typedef std::aligned_storage_t< 1, std::max( alignof (Buf<T>), alignof (T) ) >
        unit_type; // lowest-common-denominator type for aligned allocation

std::size_t unit_count // number of unit_type array elements needed
    = ( sizeof (Buf<T>) + sizeof (T) * n // = actual used storage
           + sizeof (unit_type) - 1 )    //   + alignment padding
      / sizeof (unit_type);              // divided by element size

typedef typename std::allocator_traits< SomeAllocator >::rebind_alloc< unit_type >
        rebound;

rebound a( alloc_parm ); // Retain this somewhere if statefulness is allowed.
ptr = std::allocator_traits< rebound >::allocate( a, unit_count );

(记住,所有的分配器访问都通过allocator_traits进行!)

0

我认为符合标准的分配器是否符合STL的要求?由于您不需要使用STL数据结构,因此没有必要满足其要求,尽管您可以这样做,因为我认为这是一种很好的方法。在这种情况下,您可以使用带有自定义STL样式分配器的std :: vector作为模板参数来实现缓冲区。关于operator new和operator new []的对齐保证,建议您查看:

C++的new操作返回地址的对齐保证吗?

如果您对原始类型(例如double等)的对齐担忧,那么您可以通过std :: align来解决,如http://en.cppreference.com/w/cpp/memory/align所示。

然而,如果您有更奇怪/更大的对齐要求,例如将每个元素对齐到缓存行等,或者T是一个大小为sizeof(T)mod alignment != 0的类型,则在分配T数组时可能会遇到问题。在这种情况下,即使数组的第一个元素已对齐以满足要求,也并不意味着所有后续元素都将对齐。


0

恐怕您对C++标准所需的内容做出了不必要的假设。您尝试做的事情可能并不是通用的。

默认分配器(new或malloc)需要返回指向适当对齐的内存块的指针,以满足任何具有基本对齐要求的完整对象类型。大小必须至少与请求的大小一样大。自定义分配器具有不同的要求,具体取决于它们分配的内容。一个类型的分配器不能保证返回适合另一个类型对齐的存储空间。当然,如果您正在实现自定义分配器,可以确保它返回您所需的内容。

编译器需要满足一些关于内存布局的约束条件,但它不会保证将某些内容放置在紧随其他内容之后的内存中。可能会插入填充字节以满足对齐要求。

最近的C++标准为处理对齐提供了相当多的支持。也许在其中有一个答案适合您。我怀疑背后存在一些您没有告诉我们的要求。也许还有其他方法可以解决问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接