我最近遇到了Howard Hinnant的short_alloc,这是我见过的自定义分配器的最好例子。
但是当我花更多时间研究代码以将其集成到我的个人项目中时,我发现提供基于堆栈的分配的SomeClassName::
类可能并不总是返回正确对齐的内存。实际上,我担心只有第一次分配才能保证适当对齐(因为缓冲区本身具有强制对齐),请参阅下面的相关代码片段:
arena
我可以考虑一些方法来解决这个问题(以一些内存浪费为代价),最简单的方法是将template <std::size_t N>
class arena
{
static const std::size_t alignment = 16;
alignas(alignment) char buf_[N];
char* ptr_;
//...
};
template <std::size_t N>
char*
arena<N>::allocate(std::size_t n)
{
assert(pointer_in_buffer(ptr_) && "short_alloc has outlived arena");
if (buf_ + N - ptr_ >= n)
{
char* r = ptr_;
ptr_ += n;
return r;
}
return static_cast<char*>(::operator new(n));
}
函数中的size
四舍五入为allocate/deallocate
的倍数}。
但在改变任何事情之前,我想确保我在这里没有遗漏任何东西......
答案 0 :(得分:32)
此代码是在我的工具箱中有std::max_align_t
之前编写的(现在位于<cstddef>
)。我现在写这个:
static const std::size_t alignment = alignof(std::max_align_t);
在我的系统上完全等同于当前的代码,但现在更加便携。这是new
和malloc
保证返回的对齐方式。一旦你有了这个&#34;最大程度地对齐&#34;缓冲区,你可以在其中放入任何一种类型的数组。但是,对于不同的类型,您不能使用相同的arena
(至少不是具有不同对齐要求的不同类型)。因此,也许最好在第二个arena
上模板size_t
,这等于alignof(T)
。这样,您可以防止具有不同对齐要求的类型意外使用相同的arena
:
arena<N, alignof(T)>& a_;
假设来自arena
的每个分配具有相同的对齐要求,并且假设缓冲区是最大对齐的,那么缓冲区中的每个分配都将适当地对齐T
。
E.g。在我的系统alignof(std::max_align_t) == 16
上。具有此对齐的缓冲区可以包含以下数组:
alignof == 1
的类型。alignof == 2
的类型。alignof == 4
的类型。alignof == 8
的类型。alignof == 16
的类型。由于某些环境可能支持具有&#34; super alignment&#34;要求,增加安全预防措施(例如在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};
这不一定是这些分配器中的最后一个词。但希望这是一个可以构建自定义分配器的坚实基础。