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

Will std::move() upon object construction in return statement help or prevent RVO?

由于社区的广泛响应,我提出这个问题是希望揭穿堆栈溢出用户针对具体实现的响应。

以下哪个是最佳实践(提供最大的优化)?

// 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 的直接、恭敬的回答。 [已接受的答案]

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

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

在 C++11 或 14 中,它要求对象是可移动或可复制的。遗漏;没有移动或复制完成。

在 C++17 中,这里没有移动或复制来省略。

在每种情况下,在实践中,MyObject 直接构造在 return 值中。

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

这在 C++03 中无效。

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

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

与版本 1 相同,除了 C++17 的行为类似于 C++11/14。另外,这里的省略比较脆弱;看似无害的更改可能会迫使编译器实际移动 obj.

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

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

在实践中,这与版本 2 一样。在构造 obj 时发生额外的移动(在 C++03 中复制),但它被省略,因此在运行时没有任何反应。

Elision 允许消除 copy/move 的副作用;对象生命周期合并为一个对象,并且 move/copy 被消除。构造函数仍然存在,只是从未被调用。

回答

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 移动到 return 值中,而不是将 obj 和 return 值一起删除。