是`x = std :: move(x)`undefined?

x是以前已经初始化的某种types的variables。 是以下行:

 x = std::move(x) 

未定义? 标准在哪里呢?它是怎么说的?

不,这不是未定义的行为,它将是实现定义的行为,它将取决于如何执行移动分配。

与此有关的是LWG第2468号问题:自动分配图书馆types ,注意这是一个积极的问题,并没有一个正式的提案,所以这应该被认为是指示性的而不是确定性的,但它指出涉及的部分标准图书馆,并指出他们目前的冲突。 它说:

假设我们写

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

v应该是什么状态? 这个标准没有提到任何关于自动分配的具体内容。 这个标准的几个部分都有相关的文字,不清楚如何调和它们。

[…]

从文中不清楚如何把这些东西放在一起,因为不清楚哪一个优先。 也许17.6.4.9 [res.on.arguments]胜(它强加了MoveAssignable需求中没有提到的隐式前置条件,所以v = move(v)是未定义的),或者可能是23.2.1 [container.requirements.general ](它明确地给出了Container :: operator =的额外保证,超出了对一般库函数的保证,所以v = move(v)是一个no-op)或者别的什么。

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

并提出:

非正式地更改MoveAssignable和Container需求表(以及任何其他需要提及移动赋值的需求表),使其明确指出x = move(x)是定义的行为,并使x处于有效但未指定的状态。 这可能不是这个标准今天所说的,但这可能是我们的目标,并且与我们告诉用户以及实际执行的内容是一致的。

请注意,对于内置types,这基本上是一个副本,我们可以从草稿C ++ 14标准章节5.17 [expr.ass]中看到

在简单赋值(=)中,expression式的值将replace左操作数引用的对象的值。

这与课堂上的情况不同,其中5.17表示:

如果左操作数是types的,则该类应该是完整的。 对类的对象的赋值由复制/移动赋值运算符(12.8,13.5.3)定义。

请注意,clang有一个自动移动警告 。

它会调用X::operator = (X&&) ,所以它的实现由pipe理这个案例来完成(就像X::operator = (const X&)

所有这一切都是调用X::operator=(X&&) (与左值合格的“ *this ”)。

在原始types上, std::move几乎没有兴趣,并且根本不与=进行交互。 所以这只适用于类types的对象。

现在,对于std一个types(或者由其中的一个模板生成),对象从趋向中move到未指定状态(有效)。 这不是未定义的行为,但它不是有用的行为。

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

一般来说,当从一个对象move时,你正在向消费者传达“你不关心对象在之后的状态”。 因为你(通常)在操作完成之后(正如你指定的那样) 一直在关心x所处的状态,所以使用x = std::move(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的状态,我们将在下一行中恢复它。