是`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
的状态,我们将在下一行中恢复它。