为什么`std :: pmr :: polymorphic_allocator`不会在容器移动时传播?

时间:2017-07-13 18:10:09

标签: c++ c++17 allocator

来自http://en.cppreference.com/w/cpp/memory/polymorphic_allocator

  

polymorphic_allocator不会在容器副本分配,移动分配或交换上传播。因此,移动分配polymorphic_allocator - 使用容器可以抛出并交换两个polymorphic_allocator - 使用其分配器不会比较相等的容器导致未定义的行为。

为什么我会想要这种行为?这不仅会在交换中引入无偿的未定义行为,更重要的是,对于我的目的,它意味着std::pmr::vector实际上是一个不可移动的可分配类型。我的意思是,它是可移动的,但几乎可以保证效率低下。

std::vector<int> v = {1, 2, 3};
std::vector<int> w;
w = std::move(v);  // nocopy, nothrow

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w;
w = std::move(v);  // yescopy, yesthrow

我的猜测是,这是处理所有权问题的原始尝试。在上面的第二个示例中,v包含对mr的引用,但v实际上并不拥有 mr。允许非拥有引用在整个系统中未经检查地传播将倾向于引入许多微妙的错误。因此,设计人员决定不要传播mr的引用,而不是发明拥有分配器。这最终产生了不良影响,例如移动 - 分配矢量现在复制其数据;但是你最终还是没有那么多关于内存资源的悬空指针。 (一些是,但没有那么多。)

P.S。我已经看到你可以提前设置分配器来避免复制/抛出,如下所示:

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w(v.get_allocator());
w = std::move(v);  // nocopy, nothrow

2 个答案:

答案 0 :(得分:7)

allocator_traits<>::propagate_on_container_copy/move_assignment/swap是分配器的静态属性。根据设计,多态分配器的属性在运行时中定义。一些多态分配器可以传播给其他人,有些则不能传播。

因此,在编译时不可能知道这些属性。因此,PMR分配器必须在编译时假设最坏的情况:没有传播。

让我们举个例子,改变一下:

std::pmr::monotonic_buffer_resource mr(1000);
std::pmr::vector<int> v( {1, 2, 3}, &mr );
std::pmr::vector<int> w;
auto a1 = w.get_allocator();
w = std::move(v);
assert(w.get_allocator() == a1);

C ++标准要求这不会断言。赋值不会移动容器的分配器;这就是容器赋值如何与C ++中的分配器一起使用。

因此,目标容器的分配器可以处理源容器的内存,也可以不处理。如果不能,则目标容器必须分配内存并从源中复制/移动对象。

移动赋值运算符是否为noexcept是运算符的静态属性。由于PMR在运行时无法知道它们是否可以传播存储,因此容器必须将其移动赋值运算符指定为抛出移动赋值。

在运行时,它实际上可以决定是否可以进行传播。如果有可能,那么它将采用更有效的途径。

答案 1 :(得分:0)

UPD:感谢@cpplearner-我的示例不正确-vector将传递它自己的分配器。

您是正确的,pmr不是常规,这是一个问题(有更多不愉快的代码示例)。

避免的问题是您不想混合使用分配器。

假设我们传播。这段代码怎么样?

class X {
  std::pmr::vector<std::pmr::vector<Y>> table;
 public:
  /**/
  void new_data(std::pmr::vector<Y> column) {
    table.emplace_back(std::move(column));
  }
};

使用pmr的 only 正确方法是:

void new_data(std::pmr::vector<Y> column) {
   table.emplace_back(
     std::move(column), table.get_allocator());
}

我并不是说我同意这一点,但这是动机。