这段代码是在我的工具箱中有std::max_align_t(现在位于<cstddef>)之前编写的。我现在可以这样写:
static const std::size_t alignment = alignof(std::max_align_t);
在我的系统上,它与当前代码完全相同,但现在更便携。这是new 和malloc 保证返回的对齐方式。一旦你有了这个“最大对齐”的缓冲区,你就可以在其中放入任何一种类型的数组。但是您不能对不同的类型使用相同的arena(至少不能使用具有不同对齐要求的不同类型)。出于这个原因,也许最好在第二个size_t 上模板arena,它等于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));
}
感谢别名模板,这个分配器比以往更容易使用。例如:
// Create a vector<T> template with a small buffer of 200 bytes.
// Note for vector it is possible to reduce the alignment requirements
// down to alignof(T) because vector doesn't allocate anything but T's.
// And if we're wrong about that guess, it is a comple-time error, not
// a run time error.
template <class T, std::size_t BufSize = 200>
using SmallVector = std::vector<T, short_alloc<T, BufSize, alignof(T)>>;
// Create the stack-based arena from which to allocate
SmallVector<int>::allocator_type::arena_type a;
// Create the vector which uses that arena.
SmallVector<int> v{a};
这不一定是此类分配器的最终决定。但希望这是您构建自定义分配器的坚实基础。