在return语句中构造对象时std :: move()会帮助还是阻止RVO?

时间:2017-07-27 00:07:24

标签: c++ rvo stdmove

由于来自社群的广泛响应,我要求这样做,希望能够从堆栈溢出用户中揭示特定于实现的响应。

哪些是最佳实践(提供最佳优化)?

// version 1
MyObject Widget::GetSomething() {
  return MyObject();
}

// version 2
MyObject Widget::GetSomething() {
  return std::move(MyObject());
}

// version 3
MyObject Widget::GetSomething() {
  auto obj = MyObject()
  return obj;
}

// version 4
MyObject Widget::GetSomething() {
  auto obj = MyObject()
  return std::move(obj);
}

修改 感谢Yakk的直接,尊重的回答。 [接受回答]

1 个答案:

答案 0 :(得分:2)

// version 1
MyObject Widget::GetSomething() {
  return MyObject();
}

在C ++ 03中,这需要MyObject可复制。在运行时,不会使用任何具有合理设置的“真实”编译器进行复制,因为此处标准允许省略。

在C ++ 11或14中,它要求对象是可移动的或可复制的。 Elision仍然存在;没有移动或复制。

在C ++ 17中,没有移动或复制到此为止。

在每种情况下,实际上,MyObject直接在返回值中构建。

// version 2
MyObject Widget::GetSomething() {
  return std::move(MyObject());
}

这在C ++ 03中无效。

在C ++ 11及更高版本中,MyObject被移入返回值。移动必须在运行时发生(除非消除)。

// version 3
MyObject Widget::GetSomething() {
  auto obj = MyObject();
  return obj;
}

与版本1相同,但C ++ 17的行为与C ++ 11/14相同。此外,这里的省略更脆弱;看似无害的变化可能会迫使编译器实际移动obj

理论上,在C ++ 11/14/17(以及C ++ 03中的2个副本)中省略了2个动作。第一个省略是安全的,第二个是脆弱的。

// version 4
MyObject Widget::GetSomething() {
  auto obj = MyObject();
  return std::move(obj);
}

在实践中,这与版本2的行为相似。在构造obj时会发生额外的移动(C ++ 03中的复制)但是它被省略了,所以在运行时没有任何事情发生。

Elision可以消除复制/移动的副作用;将对象生命周期合并到一个对象中,并删除移动/复制。构造函数仍然必须存在,它永远不会被调用。

答案

1和3都将编译为相同的运行时代码。 3稍微脆弱一点。

2和4都编译为相同的运行时代码。它应该永远不会快于1/3,但是如果移动可以被编译器消除,证明不执行它就像 - 如果这样做,它可以编译为与1/3相同的运行时代码。这远非保证,而且极其脆弱。

所以1>=3>=2>=4在实践中是更快到更慢的顺序,其中速度相同的“更脆弱”的代码是<=

作为一个可以使3慢于1的情况的例子,如果你有一个if语句:

// version 3 - modified
MyObject Widget::GetSomething() {
  auto obj = MyObject();
  if (err()) return MyObject("err");
  return obj;
}

突然之间,许多编译器将被迫将obj移动到返回值中,而不是将obj和返回值一起移动。