【问题标题】:Example usage of propagate_on_container_move_assignmentpropagate_on_container_move_assignment 的示例用法
【发布时间】:2015-02-12 18:46:18
【问题描述】:

我正在尝试了解如何正确编写 AllocatorAware 容器。

我的理解是propagate_on_container_move_assignment typedef表示在Container本身被移动赋值时是否需要复制某个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;
}

这是正确的吗?

另外,这里的另一个混淆来源是嵌套的 typedef propagate_on_container_move/copy_assignment 专门谈论 assignment...但是构造函数呢? AllocatorAware 容器的移动构造函数或复制构造函数需要检查这些类型定义吗?我认为这里的答案是 yes...,意思是,我还需要写:

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
         );
      }
}

【问题讨论】:

    标签: c++ c++11 allocator


    【解决方案1】:

    我建议研究libc++&lt;vector&gt; 标头。您必须处理 std::lib 实现者必须使用的所有讨厌的下划线。但是 libc++ 有一个符合 C++11 的实现,供检查使用。

    移动赋值运算符

    容器移动赋值运算符必须处理三种不同的可能性:

    1. propagate_on_container_move_assignment 是真的。
    2. propagate_on_container_move_assignment 为假,来自 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 为假时,您必须在运行时在情况 2 和 3 之间进行选择,因为分配器比较是运行时操作。

    在情况 2 中,您可以执行与情况 1 相同的操作,除了不要移动分配分配器。跳过这一步。

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

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

    请注意,在情况 1 中,由于在编译时选择了算法,容器的 value_type 不必是 MoveAssignableMoveInsertable (MoveConstructible) 来移动分配容器。但是在情况 2 中,value_types do 必须是 MoveAssignableMoveInsertable (MoveConstructible),即使它们从来都不是,因为您在运行时在 2 和 3 之间进行选择时间。而3需要value_type上的这些操作来做assign

    移动赋值运算符很容易成为容器实现的最复杂的特殊成员。剩下的就简单多了:

    移动构造函数

    move构造函数只是move构造了allocator并从rhs中窃取资源。

    复制构造函数

    复制构造函数从select_on_container_copy_construction(rhs.m_alloc) 获取分配器,然后使用它为复制分配资源。

    复制赋值运算符

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

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

    【讨论】:

    • propagate_on_move_assignment 为真但 lhs 和 rhs 的分配器比较不相等的情况呢?如果propagate_on_move_assignment 为真,我们不应该首先检查分配器是否比较相等,如果它们相等,我们不需要在移动分配分配器之前释放所有内存。 (这样我们可以重用已经分配的内存)
    • 如果您的容器设计是这样的,您可以将所有内存的所有权从 rhs 转移到 lhs,那么就不需要重用 lhs 上的任何内存。但是,如果您不能传输所有内存,那么如果 rhs 不能提供内存,并且分配器相等,那么重用 lhs 上的内存会很方便。我的偏见是,当 100% 的内存可以转移时,容器设计是最好的,使 rhs 处于资源较少的状态。
    • 自 C++17 起,if constexpr 可用于分隔 {1} 和 {2,3}。
    猜你喜欢
    • 2017-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 2014-12-25
    • 1970-01-01
    相关资源
    最近更新 更多