快速移动任务与Howard Hinnant的short_alloc

时间:2013-10-07 16:46:41

标签: c++ memory-management c++11 move-semantics allocator

我正在使用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_assignmentstd::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(请注意,例如,相等不是对称的),我可以这样做吗?有优雅的解决方案吗?

1 个答案:

答案 0 :(得分:2)

两个不同的arena对象可能具有不同的生存期。依赖于不同short_alloc对象的两个不同arena对象管理具有不同生存期的内存。结果,两个具有不同short_alloc对象的std::vector对象不能简单地在它们之间移动指针。

您的骇客将无法工作,因为它是从arenanew[]分配的指针。您的黑客假设分配器成为大向量的堆分配器,但事实并非如此。如果不检查请求的大小或释放的指针,分配器不会知道这一点。

正确的解决方案是用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::mapstd::unordered_set

问题在于某些节点来自arena,有些来自堆。如果使用operator==,建议的true返回new[],那么从std::map的移动将使来自无关领域的某些节点移动目标std::map。非常意外,这是一个坏主意。这将导致一个std::map对象,该对象包含来自其自己的arena和不相关的arena的节点。无关的arena的节点将永远不会被std::map释放。这些不良节点只有在其分配的arena死亡时才会被释放。

问题中提出的技术已被完全破坏。除了std::vector以外,几乎所有其他内容都以令人惊讶的方式导致分配不一致。我会强烈建议不要这样做。