让x
成为先前已初始化的某种类型的变量。是以下行:
x = std::move(x)
未定义?这个标准在哪里?它对它有什么看法?
答案 0 :(得分:52)
不,这不是未定义的行为,它将是实现定义的行为,它将取决于如何实现移动分配。
与此相关的是LWG issue 2468: Self-move-assignment of library 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 ] wins(它显然为Container :: operator =提供了额外的保证,超出了一般保证库函数的保证,所以v = move(v)是一个no-op),或者其他东西。
在我检查的现有实现上,为了它的价值,v = move(v)似乎清除了向量;它没有保持向量不变,也没有导致崩溃。
并建议:
非正式地:更改MoveAssignable和Container需求表(以及提及移动分配的任何其他需求表,如果有的话),以明确x = move(x)是已定义的行为,并使x保持有效但未指定的状态。这可能不是标准今天所说的,但它可能与我们的预期相符,并且与我们告诉用户的内容以及实际执行的内容一致。
注意,对于内置类型,这基本上是一个副本,我们可以从草案C ++ 14标准部分5.17
[expr.ass] 中看到:
在简单赋值(=)中,表达式的值将替换左侧引用的对象的值 操作数。
与类的情况不同,其中5.17
表示:
如果左操作数是类类型,则类应完整。定义了对类的对象的赋值 通过复制/移动赋值运算符(12.8,13.5.3)。
注意,clang有self move warning:
日志: 向Clang添加一个新警告-Wself-move。
-Wself-move与-Wself-assign类似。此警告在触发时触发 尝试将值移动到自身。有关错误,请参阅r221008 会被这个警告抓住。
答案 1 :(得分:13)
它将调用onEnd
,因此由实施来管理此案例(正如X::operator = (X&&)
所做的那样)
答案 2 :(得分:11)
所有这一切都是致电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);
}
如果您在同一个对象上调用swap两次,则中间行lhs = std::move(rhs)
会执行x = std::move(x)
。
但请注意,在此行完成后,我们不关心状态x
;我们已将x
的状态存储在tmp
中,我们将在下一行恢复它。