【问题标题】:Moving from a moved-from object从移出对象移动
【发布时间】:2017-11-07 22:09:05
【问题描述】:

考虑下面的代码。

using T = std::string;

void rotate_left(std::vector<T>& v) {
    T temp = std::move(v[0]);
    for (size_t i=0; i+1 < v.size(); ++i) {
        v[i] = std::move(v[i+1]);
    }
    v.back() = std::move(temp);
}

int main()
{
    std::vector<T> v(3);  // a vector of three Ts
    T x = std::move(v[1]);  // move-from the second element
    rotate_left(v);
    // Can we now say that v[0] is in a moved-from state, or did we
    // get undefined behavior when we moved from v[1] a second time?
}

rotate_left 函数实际上只是将向量中的所有内容向下移动一个位置(然后将第一个元素放在末尾)。我的问题是,当向量中的一个元素处于“已移出状态”时,此函数是否已定义行为?

这与“自移动”相关但不完全相同。在这种情况下,我们从一个被移动的对象移动到另一个被移动的对象,我的问题是我们是否可以依靠这个让两个对象仍然处于某个被移动的状态,或者“赋值来自”是否是“具有先决条件”的操作之一,因此不能用于任意移出的对象。

我很清楚

  • 这对于像unique_ptr这样的健全的库类型来说非常好
  • 这个如果我定义合理的话,对于我自己的用户定义类型来说非常好
  • 如果我疯狂地定义它们,这对我自己的用户定义类型有问题
  • 这对于我在 libstdc++ 和 libc++ 上尝试过的所有库类型都很好

所以我真正想要的是:

  • 标准中的具体措辞证明这必须完全适用于所有 STL 类型,或者
  • 一个 STL 或 Boost 类型“在野外”的具体示例,其中此代码肯定有问题

我从this bug 推断出_GLIBCXX_DEBUG 会检查self-move,但我可以确认它不会检查move-from-moved;这是因为他们认为 move-from-moved 是安全且合法的,还是只是因为还没有人编写代码来检查它?

【问题讨论】:

  • 不是一个答案,因为语言律师,但我希望有效但未指定的价值得到转移
  • 这种棘手。在 C++11 及更高版本中,vector 仅要求 T 可擦除,但您正在寻找的保证适用于 MoveConstructable/MoveAssignable @987654322 的对象@

标签: c++ language-lawyer move-semantics


【解决方案1】:

您的程序的行为定义明确,但未指定。不同之处在于v 有一个有效的状态,你只是不知道它是什么状态而不检查它。

标准的这个角落一直存在争议,措辞一直难以正确。但我相信最新的措辞(C++17)是我们迄今为止最好的尝试,所以我会引用它(N4660)。

免责声明,我实际上链接到 N4659,因为 N4660 不公开。差别不大。

从 20.5.5.15 Moved-from 库类型的状态 [lib.types.movedfrom]

1 C++ 标准库中定义的类型的对象可以从 (15.8) 中移出。移动操作可以显式指定或隐式生成。除非另有说明,否则此类移出的对象应处于有效但未指定的状态。

本段是对 std::lib 定义的所有类型的概括性声明,即移出对象不是“毒药”,您只是不知道它们的价值。

此外,标准中定义的每个算法(包括成员函数)都可能有一个先决条件列表,这些先决条件在调用该算法之前必须为真。如果没有列出前提条件,则意味着您始终可以调用该函数。

在 std::lib 中定义的所有类型的移动赋值永远不会为左侧或右侧参数列出任何先决条件。

很难引用不存在的东西,但这就是本规范的工作方式。

进一步概括,以下部分指的是与 std::lib 一起使用的所有类型(即用户提供的):

20.5.3.1 模板参数要求 [utility.arg.requirements]

表 23 - MoveConstructible 要求 [moveconstructible]

T u = rv;
T(rv);

rv 的状态在后置条件中未指定。

表 25 - MoveAssignable 要求 [moveassignable]

t = rv

只有当trv不引用同一个对象时,t才等价于赋值前rv的值。

之后,rv 的状态未指定(无论trv 是否指同一个对象)。

此外,本节还指出(注释是非规范性的,规范性文本通常出现在其他地方)rv 仍然必须满足与它一起使用的库组件(算法)的要求,即使它已从.

例如,允许 std::sort 从值 x 移动,然后在比较表达式中使用该 xx 必须是 LessThanComparable,无论 x 是否被移出。只有x 的值未指定。

bool b = x < x;  // b must be false, no matter what!

【讨论】:

  • 听起来不错!仅供参考,这个问题在 re SG14 ring_span::normalize() 中出现了 over here,这几乎完全是“std::rotate 在一堆移动的对象上”。我认为那里没有什么会使这个答案的要点无效?
  • @Quuxplusone:正确。在std::terms 中,如果ring_span::normalize 需要MoveAssignable,那么T 必须是move-assignable-from,即使处于move-from 状态。 ring_span::normalize 当然可以指定其他要求。 std::swap(x, x)(通用交换)是另一个从被移动对象移动分配的算法示例。
  • 是的,但是std::swap(x, x) 是在实践中确实中断的事情之一(它执行自移动分配,根据_GLIBCXX_DEBUG 是非法的),所以类似于std::swap(x, x)更有可能让我重新担心而不是让我放心。 :P
  • _GLIBCXX_DEBUG 被误导。 std::swap(x, x) 由标准保证是(任意昂贵的)身份操作。
  • 在 gcc-4.9 上对我来说还不错:wandbox.org/permlink/UfcWieN8fwqD4g2L
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-19
  • 1970-01-01
  • 2013-02-18
  • 2020-04-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多