传播容器移动赋值的示例用法

27

我试图理解如何正确编写 AllocatorAware 容器。

我的理解是,propagate_on_container_move_assignment typedef 指示是否需要在容器自身被 move-assigned 时复制某个 Allocator 类型。

因此,由于我找不到任何示例,我自己尝试了以下内容:

给定容器类型 Container,一个 Allocator 类型 allocator_type 和一个内部的 allocator_type 数据成员 m_alloc

Container& operator = (Container&& other)
{
  if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
  {
     m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction(
      other.m_alloc
     );
  }

  return *this;
}

这是否正确?

此外,这里另一个令人困惑的来源是嵌套的 typedefs propagate_on_container_move/copy_assignment 是在特别讨论 赋值操作 ... 但是构造函数呢?AllocatorAware 容器的移动构造函数或拷贝构造函数 也需要 检查这些 typedefs 吗?我认为答案应该是 是的... 这意味着,我还需要写:

Container(Container&& other)
{
      if (std::allocator_traits<allocator_type>::propagate_on_container_move_assignment::value)
      {
         m_alloc = std::allocator_traits<allocator_type>::select_on_container_copy_construction(
          other.m_alloc
         );
      }
}
1个回答

44

我建议学习libc++<vector>头文件。你需要处理所有恶心的下划线,这是std::lib实现者所必须使用的。但是,libc++有一个符合C++11标准的实现,因此可以进行检查。

移动赋值运算符

容器移动赋值运算符必须要处理三种不同的情况:

  1. propagate_on_container_move_assignment为true。
  2. propagate_on_container_move_assignment为false,且lhs和rhs的分配器比较相等。
  3. propagate_on_container_move_assignment为false,且lhs和rhs的分配器比较不相等。

在可能的情况下,应该在编译时而不是运行时做出这三种情况的决策。具体来说,应该在 {1} 和 {2, 3} 这两个集合间进行选择,因为 propagate_on_container_move_assignment 是一个编译时常量。通常使用tag dispatching进行编译时常量的编译时分支,而不是像您展示的那样使用 if 语句。

在这些情况下都不应该使用 select_on_container_copy_construction。该函数仅适用于容器的复制构造函数。

在情况1中,lhs首先应该使用lhs的分配器来释放已分配的所有内存。这必须是首先完成的,因为rhs分配器可能无法稍后释放该内存。然后将lhs分配器从rhs分配器中移动赋值(就像任何其他移动赋值一样)。然后转移内存所有权从rhs容器到lhs容器。如果你的容器设计是rhs容器不能处于无资源状态(在我看来是一个糟糕的设计),那么可以通过被移后的rhs分配器分配新的资源给rhs容器。

propagate_on_container_move_assignment 为false时,必须在运行时选择2和3两种情况之间,因为分配器比较是运行时操作。

在情况2中,你可以做与情况1相同的事情,除了不要移动分配器。只需跳过此步骤即可。

在情况3中,你无法将任何内存的所有权从rhs容器转移到lhs容器。你唯一能做的就是如同:

assign(make_move_iterator(rhs.begin()), make_move_iterator(rhs.end()));

需要注意的是,在情况1中,由于算法在编译时已被选择,容器的value_type不需要是MoveAssignableMoveInsertableMoveConstructible),即使移动分配容器。但是在情况2中,value_type必须是MoveAssignableMoveInsertableMoveConstructible),即使它们永远不会是这样,因为您正在运行时选择2和3之间。而情况3需要对value_type进行这些操作才能执行assign

移动赋值运算符是实现容器最复杂的特殊成员函数。其他成员函数要简单得多:

移动构造函数

移动构造函数只需移动构造分配器并从rhs中窃取资源。

拷贝构造函数

拷贝构造函数从select_on_container_copy_construction(rhs.m_alloc)获取其分配器,然后使用该分配器为副本分配资源。

拷贝赋值运算符

拷贝赋值运算符必须首先检查propagate_on_container_copy_assignment是否为true。如果是,并且lhs和rhs的分配器比较不相等,则lhs必须首先释放所有内存,因为在拷贝分配器之后它将无法再这样做。接下来,如果propagate_on_container_copy_assignment,则复制分配器,否则不进行复制。然后复制元素:

assign(rhs.begin(), rhs.end());

1
如果propagate_on_move_assignment为真,但是lhs和rhs的分配器比较不相等怎么办?如果propagate_on_move_assignment为真,我们不应该先检查分配器是否相等吗?如果它们相等,我们就不需要在移动分配分配器之前释放所有内存。(这样我们就可以重用已经分配的内存) - Siler
如果您的容器设计使得您可以将所有内存的所有权从rhs转移到lhs,则无需在lhs上重用任何内存。但是,如果您无法转移所有内存,则如果lhs无法由rhs提供并且分配器相等,则重用lhs上的内存可能会很方便。我的偏见是,当可以转移100%的内存时,容器设计最佳,使rhs处于无资源状态。 - Howard Hinnant
2
自C++17以来,可以使用if constexpr来分离{1}和{2,3}。 - zjyhjqs
在我看到答案底部之前,我猜到这是Howard Hinnant的回答! - Nick Deguillaume

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接