是否x = std::move(x)未定义

Is x = std::move(x) undefined?

本文关键字:未定义 move std 是否      更新时间:2023-10-16

x为已初始化的某种类型的变量。

x = std::move(x)

定义?这在标准中处于什么位置,它是怎么说的?

不,这不是未定义行为,它将是实现定义的行为,它将取决于如何实现move赋值。

与此相关的是LWG问题2468:库类型的自移动分配,注意这是一个活跃的问题,没有官方提案,所以这应该被认为是指示性的,而不是决定性的,但它确实指出了标准库所涉及的部分,并指出它们目前存在冲突。它说:

假设我们写

vector<string> v{"a", "b", "c", "d"};
v = move(v);

v的状态应该是什么?标准上什么也没说具体到自移动分配。有几个是相关的文本部分标准,但不清楚如何协调它们。

[…]

从文本中不清楚如何将这些片段组合在一起,因为不清楚哪个优先。可能是17.6.4.9 [res.on]。(它强加了一个隐含的先决条件,在MoveAssignable需求中没有提到,所以v = move(v)是未定义的),或者23.2.1 [container.requirements]。general]获胜(它显式地为Container::operator=提供了额外的保证,超出了一般标准库函数的保证,因此v = move(v)是无操作的),或者可能是其他的东西。

在我检查的现有实现中,不管它的价值是什么,v = move(v)似乎清除了向量;它没有保持矢量不变,也没有导致崩溃。

并提出:

非正式地:改变MoveAssignable和Container需求表(以及任何其他提到move赋值的需求表,如果有的话),使其明确x = move(x)是定义的行为,它使x处于有效但未指定的状态。这可能不是今天的标准说的,但这可能是我们想要的它与我们告诉用户的和实现实际做的是一致的。

注意,对于内置类型,这基本上是一个拷贝,我们可以从c++ 14标准草案5.17 [express .ass]: 看到

在简单赋值(=)中,表达式的值替换左边所引用对象的值操作数。

这与类的情况不同,其中5.17表示:

如果左操作数为类类型,则该类完成。定义了对类对象的赋值通过复制/移动赋值操作符(12.8,13.5.3)。

注意,clang有一个自我移动警告:

日志:给Clang添加一个新的警告,- self-move。

- self-move类似于- self-assign。在以下情况下触发此警告试图将值移动到自身。参见r221008会被这个警告抓住。

它将调用X::operator = (X&&),因此由实现来管理这种情况(就像X::operator = (const X&)一样)

调用X::operator=(X&&)(左值限定为" *this ")。

在基本类型上,std::move没有什么作用,并且根本不与=交互。所以这只适用于类类型的对象。

现在,对于std中的类型(或由其模板之一生成的类型),move d中的对象往往处于未指定(但有效)的状态。这不是未定义的行为,但不是有用的行为。

必须检查每个给定X::operator=(X&&)的语义,检查std中的每个类型对于堆栈溢出答案来说"太宽泛"。他们甚至可能自相矛盾。

一般来说,当move从一个对象中返回时,你是在告诉消费者"你不关心对象之后处于什么状态"。因此,使用x = std::move(x)是不礼貌的,因为您(通常)确实关心操作完成后x处于什么状态(当您为其赋值时)。在同一操作中,将同一对象既用作左值又用作右值,这不是一个好做法。

一个有趣的例外是默认的std::swap,它是这样的:
template<class T>
void swap(T& lhs, T& rhs) {
  T tmp = std::move(lhs);
  lhs = std::move(rhs);
  rhs = std::move(tmp);
}

中间行lhs = std::move(rhs),如果在同一对象上调用两次swap,则执行x = std::move(x)

请注意,我们并不关心x在这一行完成后处于什么状态;我们已经在tmp中存储了x的状态,我们将在下一行恢复它。