在我未掌握std::max_align_t
(现在位于<cstddef>
中)之前,我编写了这段代码。现在,我会将其编写为:
static const std::size_t alignment = alignof(std::max_align_t);
在我的系统上,这与当前代码完全相同,但现在更具可移植性。这是new
和malloc
保证返回的对齐方式。一旦您拥有了这个“最大对齐”缓冲区,您可以在其中放置任何一种类型的数组。但您不能为不同类型(至少对于具有不同对齐要求的不同类型)使用相同的arena
。因此,也许最好在第二个size_t
上模板化arena
,该size_t
等于alignof(T)
。通过这种方式,您可以防止将相同的arena
意外地用于具有不同对齐要求的类型:
arena<N, alignof(T)>& a_;
假设来自
arena
的每个分配具有相同的对齐要求,并且假设缓冲区最大对齐,则缓冲区中的每个分配都将适当地对齐为
T
。
例如,在我的系统上,
alignof(std::max_align_t) == 16
。 具有此对齐方式的缓冲区可以容纳以下数组:
- 具有
alignof == 1
的类型。
- 具有
alignof == 2
的类型。
- 具有
alignof == 4
的类型。
- 具有
alignof == 8
的类型。
- 具有
alignof == 16
的类型。
由于某些环境可能支持具有“超级对齐”要求的类型,因此添加(例如在
short_alloc
内)一个额外的安全预防措施是明智的。
static_assert(alignof(T) <= alignof(std::max_align_t), "");
如果你非常谨慎,你也可以检查
alignof(T)
是2的幂次方,尽管C++标准本身保证这总是成立的([basic.align]/p4)。
更新
我仔细研究了这个问题,并认为将请求的分配大小舍入到下一个
alignment
(正如OP建议的那样)是最佳解决方案。我已经在我的网站上更新了
"short_alloc"。
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
对于一些特殊情况,你知道不需要最大限度对齐分配(例如vector<unsigned char>
),可以简单地调整alignment
。并且可以让short_alloc::allocate
将alignof(T)
传递给arena::allocate
,并且assert(requested_align <= alignment)
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n, std::size_t requested_align)
{
assert(requested_align <= alignment);
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
n = align_up(n);
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
这将使您有信心,如果您将alignment
向下调整,您没有将其调整得太低。
再次更新!
我已经根据这个优秀的问题更新了description和code这个分配器的内容(我忽略了这段代码多年)。
之前更新中提到的对齐检查现在是在编译时完成的(编译时错误总是优于运行时错误,即使是断言)。
现在,arena
和short_alloc
都以模板形式提供对齐方式,因此您可以轻松地自定义预期的对齐要求(如果您猜测得太低,则会在编译时捕获)。此模板参数默认为alignof(std::max_align_t)
。
arena::allocate
函数现在如下所示:
template <std::size_t N, std::size_t alignment>
template <std::size_t ReqAlign>
char*
arena<N, alignment>::allocate(std::size_t n)
{
static_assert(ReqAlign <= alignment, "alignment is too small for this arena");
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
auto const aligned_n = align_up(n);
if (buf_ + N - ptr_ >= aligned_n)
{
char* r = ptr_;
ptr_ += aligned_n;
return r;
}
return static_cast<char*>(::operator new(n));
}
感谢
别名模板(alias templates),这个分配器比以往更易于使用。例如:
template <class T, std::size_t BufSize = 200>
using SmallVector = std::vector<T, short_alloc<T, BufSize, alignof(T)>>;
SmallVector<int>::allocator_type::arena_type a;
SmallVector<int> v;
这并不一定是这些分配器的最终解决方案。但是希望这可以成为您构建自定义分配器的坚实基础。