【问题标题】:What is the post condition of a move constructor?移动构造函数的后置条件是什么?
【发布时间】:2014-12-22 03:13:01
【问题描述】:

考虑一个类:

class foo {
public:
  foo(foo &&rhs) { /* some code */ }
  ~foo() noexcept { /* code that does destruction of owning objects */ }
private:
  /* some non-trivial pointer graph like structure */
};

假设:

{
  foo &f = get_from_other_class();
  foo g = std::move(f);
  // some time later f goes our of scope or the owning object is destroyed.
}

std::move(f) 执行后,哪些后置条件适用于f

注意

我怀疑f 必须仍然是完全可破坏的(不会破坏g 的拥有内容),但我没有在C++11 标准中找到相应的引用。我正在浏览 12.8 复制和移动类对象。

【问题讨论】:

    标签: c++ c++11 language-lawyer


    【解决方案1】:

    每个对象都必须是可破坏的,除非您只动态分配它。在“移出”之后,即其值绑定到右值引用(例如移动构造函数的第一个参数),对象仍然需要有效且可破坏,但通常它可以在“未指定”中状态,您根本不应该再查看该对象并让它超出范围或重新分配它。

    某些类型提供了更强的保证,例如std::unique_ptr 承诺在移动构造时,它使移动对象处于等于nullptr 的状态。

    右值和右值引用背后的一般思想是右值应该没有别名的对象,具体来说,当一个对象绑定到一个右值引用时,就没有该对象的其他别名。标准库对其公开的右值引用接口做出这些假设。

    【讨论】:

    • 这实际上是在哪里说明的?我主要找到对复制省略的参考。和标准中的默认实现。
    • 所以从技术上讲,我可以在这个对象上调用一个新的展示位置,对吧?
    • @Alex:不完全是。该对象仍然有效并且需要被销毁。如果你想做placement-new,那么你首先必须调用析构函数。但是don't do that。只需分配给对象。
    • @Alex: [res.on.arguments]
    【解决方案2】:

    后置条件是您的移出对象处于未指定但有效的状态。正是如此:它是未指定的,这意味着它可以是你选择的任何东西(或库实现者),只要它是有效的。

    【讨论】:

      【解决方案3】:

      在 std::move(f) 执行后,哪些后置条件适用于 f?

      这在很大程度上取决于foo 的作者,但也受到对foo 提出要求的算法(标准或非标准)的限制。

      许多算法将要求在其移出状态 fooDestructible 并且可以分配一个新值。但这取决于使用foo 的上下文。

      如果foo 与标准库组件一起使用,则要求由表 20 规定——MoveConstructible 要求:

      rv的状态未指定[注意:rv必须仍然满足要求 使用它的库组件。中列出的操作 无论 rv 是否已移动,这些要求都必须按规定工作 从与否。 ——尾注]

      例如:假设您使用vector<foo> 调用std::sortstd::sort 将需要 fooSwappableMoveConstructibleDestructibleMoveAssignableLessThanComparable

      std::sortfoo 提出这些要求foo 是否处于移出状态

      严格来说,如果与std::sort 一起使用,则在移出状态时不需要将foo 设为LessThanComparable。实现者比较移出的对象是没有意义的,因为它的值是未指定的。尽管如此,C++11 和 C++14 标准目前需要LessThanComparable。结果不需要是明智的,但如果执行则需要不崩溃。未来的标准可能会放宽这一要求,但谁知道呢。

      因此,总而言之,foo 的作者可以说明允许对已移动的foo 进行哪些操作。

      并且任何算法都可以说明它对所操作的类型的要求。

      foo 满足算法要求的交集处,代码有效。 C++ 标准不考虑特殊的移出状态。

      【讨论】:

      • std::sort 在哪里要求被排序的类型是可破坏的?我找不到它。这让我很烦,因为 std::sort 要求类型是 MoveConstructible,但是 std::sort 如何使用移动构造函数,除非它最终可以销毁它构造的对象?
      • 恕我直言,这方面的标准很薄弱。但历史上的理解是整个库规范不仅需要destructible,还需要nothrow_destructible。这方面的证据(没有明确的证明恕我直言)是eel.is/c++draft/res.on.functions#2.4
      【解决方案4】:

      表 20(第 17.6.3.1 节)定义了 MoveConstructible 要求:

      +------------+--------------------------------- -----------------------------------------------+
      |表达 |后置条件 |
      +------------+------------------------------------ --------------------------------------------+
      | T u = rv; | u 相当于构造前的 rv 值 |
      | T(rv) | T(rv) 相当于构造前的 rv 值 |
      +------------+------------------------------------ ----------------------------------------+

      rv 的状态未指定 [注意:rv 必须仍然满足 使用它的库组件的要求。这 这些要求中列出的操作必须按规定工作 rv 是否已被移出。 — 尾注 ]

      注释表明,根据@Kerrek 的回答,库组件可能有不同的要求。

      【讨论】:

        【解决方案5】:

        移动的对象必须准备好让析构函数运行。

        Per Stroustrup,C++ 编程语言,2014 年第 4 版

        如果 x 被移出,x 将具有“一些移出状态”...

        “对于最有趣的情况,容器, 移出状态为“空”“...

        我倾向于认为它类似于默认的初始化变量。

        移动后内置类型的值不变。

        default moved-from state 是默认析构函数和默认复制分配正常工作的地方。

        其中一个定义是,如果析构函数运行,则以前由对象管理的任何资源将不再由对象管理;析构函数会不管它,或者不知道。我想这取决于实现/用户。

        简而言之,您可以使用x 做任何您想做的正确事情,并且应该是正确的,就像使用新变量一样。

        【讨论】:

          【解决方案6】:

          因为是move,所以在g = std::move(f);之后,f的语义内容应该是无效的,g是有效的。 move 只保留 一个 语义内容的有效副本。
          “invalidate/validate”的语义依赖于类,比如f是字符串,应该设置长度为0,如果f是线程,应该设置为std::thread()。但是,具体行为取决于类的移动构造函数
          当然f 仍然是可破坏的。

          【讨论】:

          • 重点是:“f 应该失效”这个词是什么意思。
          猜你喜欢
          • 1970-01-01
          • 2015-06-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-08-04
          • 2013-04-25
          • 2017-05-20
          相关资源
          最近更新 更多