快速移动任务与Howard Hinnant的short_alloc

mar*_*n78 11 c++ memory-management allocator move-semantics c++11

我正在使用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
Run Code Online (Sandbox Code Playgroud)

正如在这个答案中所解释的,这是由于向量的移动赋值运算符比较了两个分配器(注意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());
}
Run Code Online (Sandbox Code Playgroud)

其中,on_heap()如果分配没有使用它的竞技场检查.

这个解决方案看起来相当hackish(注意例如,平等不对称),我可以/我会通过这样做射击自己吗?有优雅的解决方案吗?

Mic*_*ler 2

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

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

正确的解决方案是用移动运算符替换分配器对象。为此,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;
Run Code Online (Sandbox Code Playgroud)

这将使移动操作符按预期工作。移动后它将开始使用另一个竞技场。


一般来说,这种分配技术非常危险,应该很少使用,因为它们存在内存相关错误的风险很高。arena例如,如果要从函数返回一个向量,则它引用即将退出的作用域上的向量的风险很高。

根据我提议的改变,风险系数稍高。超出范围的问题arena现在也涉及到按引用传递向量。当被定义在内部块中arena时,也存在超出范围的问题。arena

另一个arena超出范围的行为可能会让程序员感到惊讶,并引入错误。这就是为什么我不喜欢这个解决方案。然而,有时人们愿意在时间关键的部分编写危险的代码(在分析和分析之后)。


正如问题所示,在适用时可以将short_alloc分配器标记为分配器。可以在第一次使用 的分配后立即以这种方式标记它new[]。这对于 来说效果很好std::vector,因为它在方法调用之间只保留一块内存。尽管使用std::vector,但它与大多数其他容器不同,因为它们大多数都使用节点,例如std::mapstd::unordered_set

问题是有些节点来自堆arena,有些节点来自堆。如果使用建议operator==的 return trueif new[],则 move fromstd::map将使来自不相关竞技场的一些节点移动到 target std::map。非常出乎意料,而且是个坏主意。这将导致一个std::map对象包含来自其自身arena和不相关的节点的对象arena。不相关的节点arena永远不会被 释放std::map。这些坏节点只有当它们的分配死亡时才会被释放arena

问题中提出的技术完全被破坏了。它会以令人惊讶的方式导致除 之外的几乎任何事物的分配不一致std::vector。我会强烈建议不要这么做。