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

时间:2015-07-21 19:00:29

标签: c++ c++11 language-lawyer c++14

x成为先前已初始化的某种类型的变量。是以下行:

x = std::move(x)

未定义?这个标准在哪里?它对它有什么看法?

3 个答案:

答案 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中,我们将在下一行恢复它。