【问题标题】:Fast move assignment with Howard Hinnant's short_alloc使用 Howard Hinnant 的 short_alloc 进行快速移动分配
【发布时间】:2013-10-14 08:20:51
【问题描述】:

我正在使用 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)。由于两个分配器比较不相等(因为它们有不同的 arena),所以目标向量需要分配内存并一一移动值。

通过将相等运算符更改为

来实现所需的行为
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() 检查分配器是否没有使用它的 arena。

这个解决方案看起来相当老套(请注意,例如,平等不是对称的),我可以/会这样做吗?有没有优雅的解决方案?

【问题讨论】:

  • 这不能在不同的值类型上返回 true 吗?

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


【解决方案1】:

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

你的 hack 行不通,因为它是从 arenanew[] 分配的指针。您的 hack 假设分配器成为大向量的堆分配器,但事实并非如此。这不是分配器不检查请求的大小或释放的指针就知道的事情。

正确的解决方案是将分配器对象替换为移动运算符。为此,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==,如果使用new[],则返回true,从std::map 移动将使一些来自不相关竞技场的节点移动目标std::map。非常出乎意料,也是一个坏主意。这将产生一个std::map 对象,该对象包含来自它自己的arena 和一个不相关的arena 的节点。不相关的arena 的节点永远不会被std::map 释放。只有当它们分配的arena 死亡时,这些坏节点才会被释放。

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

【讨论】:

    猜你喜欢
    • 2013-02-15
    • 2015-04-02
    • 2016-02-16
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-25
    • 1970-01-01
    相关资源
    最近更新 更多