我正在使用Howard Hinnant的基于竞技场的小型分配器,short_alloc。
让我感到震惊的是,从一个已经超出其竞技场并因此在堆上分配的向量进行移动分配可以使用通常的快速移动分配(即,抓取目标的资源)来完成。但事实并非如此:
typedef arena<16> arena_type;
typedef short_alloc<int, 16> alloc_type;
typedef std::vector<int, alloc_type> vec_type;
arena_type arena1, arena2;
vec_type vec1(alloc_type(arena1)), vec2(alloc_type(arena2));
vec1.resize(100);
void* data = vec1.data();
vec2 = std::move(vec1);
assert(vec2.data() == data); // fails
如this answer中所述,这是由于向量的移动赋值运算符比较了两个分配器(注意propagate_on_container_move_assignment
是std::false_type
)。由于两个分配器不比较相等(因为它们具有不同的竞技场),目标向量需要分配内存并逐个移动值。
通过将等于运算符更改为
来实现所需的行为template <class T1, size_t N1, class T2, size_t N2>
bool operator==(const short_alloc<T1, N1>& x, const short_alloc<T2, N2>& y) noexcept
{
return N1 == N2 && (&x.a_ == &y.a_ || y.a_.on_heap());
}
其中on_heap()
检查分配器是否未使用其竞技场。
这个解决方案看起来相当hackish(请注意,例如,相等不是对称的),我可以这样做吗?有优雅的解决方案吗?
答案 0 :(得分:2)
两个不同的arena
对象可能具有不同的生存期。依赖于不同short_alloc
对象的两个不同arena
对象管理具有不同生存期的内存。结果,两个具有不同short_alloc对象的std::vector
对象不能简单地在它们之间移动指针。
您的骇客将无法工作,因为它是从arena
或new[]
分配的指针。您的黑客假设分配器成为大向量的堆分配器,但事实并非如此。如果不检查请求的大小或释放的指针,分配器不会知道这一点。
正确的解决方案是用move运算符替换分配器对象。为此,short_alloc
应该定义:
using propagate_on_container_move_assignment = std::true_type;
private:
arena_type * a_; // <--- instead of reference
public:
short_alloc(const short_alloc&) = default;
// !!! Don't delete the move assignment !!!
// short_alloc& operator=(const short_alloc&) = delete;
这将使移动操作员按预期工作。移动后它将开始使用其他竞技场。
arena
的可能性很高。
通过我建议的更改,风险因素会略高。 arena
超出范围的问题现在也归结为通过参考向量。在内部块中定义arena
时,也存在arena
超出范围的问题。
其他arena
超出范围的这种行为会使程序员感到惊讶,并引入错误。这就是为什么我不喜欢这种解决方案的原因。但是,有时有人愿意在时间紧迫的部分中编写危险的代码(在分析和分析之后)。
short_alloc
分配器标记为 heap 分配器(如果适用)。可以在使用new[]
的第一次分配之后立即以这种方式进行标记。这将与std::vector
一起很好地工作,因为它在方法调用之间仅保留一块内存。尽管使用了std::vector
,但是它与其他大多数容器都无法使用,因为其中大多数容器都使用节点,例如std::map
和std::unordered_set
。
问题在于某些节点来自arena
,有些来自堆。如果使用operator==
,建议的true
返回new[]
,那么从std::map
的移动将使来自无关领域的某些节点移动目标std::map
。非常意外,这是一个坏主意。这将导致一个std::map
对象,该对象包含来自其自己的arena
和不相关的arena
的节点。无关的arena
的节点将永远不会被std::map
释放。这些不良节点只有在其分配的arena
死亡时才会被释放。
问题中提出的技术已被完全破坏。除了std::vector
以外,几乎所有其他内容都以令人惊讶的方式导致分配不一致。我会强烈建议不要这样做。