【问题标题】:Is it necessary to reset std::list after been moved?移动后是否需要重置 std::list ?
【发布时间】:2019-03-12 19:42:10
【问题描述】:

我有以下代码:

std::list some_data;
...
std::list new_data = std::move(some_data);
some_data.clear();
...

问题是some_data.clear()是否有必要? (记录在案,some_data以后会重复使用)

【问题讨论】:

  • 如果列表将被重复使用,那么它会清除它。
  • 您以后不能重复使用some_dataen.cppreference.com/w/cpp/language/move_assignment
  • some_data 将处于未定义但正确的状态。更多关于这个近乎重复的内容:Reusing a moved container?
  • 使用 clear 无疑是安全的做法。但是,在移动的情况下元素会发生什么要求,这实际上意味着“有效但未指定的状态”必须是一个空列表。
  • 我会对没有将列表留空的实现感到惊讶,但这不是必需的。此外,如果一个好的编译器看到了不留下空列表的优势,它就会接受它。

标签: c++ c++11


【解决方案1】:

是的,这是必要的。

只有 std 智能指针在被移出后才能保证处于默认构造状态。

容器处于有效但未指定的状态。这意味着您只能在没有先决条件的情况下调用成员函数,例如clear,将对象置于完全已知的状态。

【讨论】:

  • @GilsonPJ 当然移动构造函数是正确定义的。它们被定义为 1) 构造的对象将具有与原始对象相同的内容,以及 2) 移动的对象将处于有效的未指定状态。
  • @GilsonPJ 不仅仅是移动构造函数在移动。另外,移动构造函数对被移动对象的命运并不真正感兴趣,并且为他们构建额外的大脑这样做会违反“你不为你不使用的东西付费”的原则。如果您想重复使用该对象,您需要付费。你不会让每个人都付钱。
  • @GilsonPJ “当我们编写移动构造函数时,我们知道它必须移动到默认条件,对吗?” 这是错误的假设。没有人强迫任何人将移动对象置于 default-constructed 状态。
  • @MikeMB 这是在哪里写的,你能引用标准吗?我对这个话题很感兴趣。
  • 标准中提到的移出对象的唯一条件是移出对象处于有效状态。这适用于所有 std 类型,但标准只能建议您对您的类型执行相同的操作。如果移动构造函数使对象处于无效状态,则 C++ 程序并非无效。这只是糟糕的 C++,每个经验丰富的 C++ 开发人员都会被这种东西呛到。
【解决方案2】:

标准(N4713)的工作草案说明了对象被移动后的状态:

20.5.5.15 库类型的移动状态 [lib.types.movedfrom]
1 C++ 标准库中定义的类型的对象可能会被移出。移动操作可以显式指定或隐式生成。 除非另有说明,否则此类移出的对象应处于有效但未指定的状态。

20.3.25 [defns.valid]
对象的有效但未指定的状态值,除了满足对象的不变量并且对象上的操作按照其类型指定的行为外,未指定对象的状态值

您可以安全地在移出容器上执行的唯一操作是那些不需要先决条件的操作。 clear 没有先决条件。它会将对象返回到可以再次使用的已知状态。

【讨论】:

  • 您是否考虑过在这种特殊情况下,除了一个空列表之外是否还有其他内容满足“有效但未指定的状态”要求?
  • @MikeMB 具有 N 个元素的列表/向量,其中对 size() 的调用返回 N,但 N 是一个未指定的数字,这是一个完全有效但未指定的状态。对于向量,容量也需要 >= N 才能满足该类不变量。
  • @rubenvb:这可能是有效的,但意味着移动构造函数会在源列表/向量中创建新的 value_type 对象,这将毫无意义。
【解决方案3】:

不,没有必要清除列表。这样做既合理又方便。

列表将有 N 个元素,每个元素取一个未指定的值,对于某些 N≥0。因此,如果您想重新填充列表,理论上您可以分配给保留的元素而不是清除并从头开始重新插入所有内容。

当然,N≠0 的可能性微乎其微,因此在实践中清除是正确的选择。

【讨论】:

    【解决方案4】:

    简答:
    是的,您应该清除它,因为标准没有明确定义移动后源列表处于什么状态。
    此外,当容器在被移出后被重用时始终调用clear() 是一个简单的规则,即使它是冗余的也不应该造成任何重大开销。

    更长的答案:
    正如我在一些 cmets 中提到的,我无法想象任何合法且合理的实现,其中源 std::list 在用作移动构造函数的源之后将是空的 std::list(感谢 @Lightness Races in Orbit用于挖掘标准)。移动构造必须在线性时间内发生,这不允许任何每个对象的操作,并且 AFAIK 也不允许有一个小的、固定大小的就地缓冲区,这可能是剩余的“僵尸”元素的来源源列表。

    但是,即使您可以在标准中找到所有这些内容,您仍然必须在每次省略代码中的 clear() 时记录这一点,并警惕有人通过替换 std::list 来重构代码使用本土容器,并不能完全满足标准中的要求(例如,利用小缓冲区优化)。另外,前面的语句只对移动construction有效。在 moveassignment 中,从容器中移出的内容绝对有可能不是空的。

    总结:即使在这种特殊情况下不使用clear 在技术上是正确的,但恕我直言不值得为此付出精神上的努力(如果您处于值得这样做的环境中,那么您就是可能将您的代码调整为标准库的特定版本,而不是标准文档)。

    【讨论】:

      【解决方案5】:

      这取决于您所说的“重用”是什么意思。有许多重用完全定义了对象的状态,而不考虑它以前的状态。

      很明显的一个是分配给一个移动的对象,这发生在天真的交换中

      template <typename T>
      void swap(T& lhs, T& rhs)
      {
          T temp = std::move(lhs);
          lhs = std::move(rhs); // lhs is moved-from, but we don't care.
          rhs = std::move(temp); // rhs is moved-from, but we don't care.
      }
      

      如果您希望通过调用some_data.push_back 来“重用”,那么是的,您应该首先clear

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-10-19
        • 2011-08-25
        • 1970-01-01
        • 1970-01-01
        • 2011-04-13
        • 2021-10-26
        • 2014-08-29
        相关资源
        最近更新 更多