在 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 值一起删除。
由于社区的广泛响应,我提出这个问题是希望揭穿堆栈溢出用户针对具体实现的响应。
以下哪个是最佳实践(提供最大的优化)?
// 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 值一起删除。