ihe*_*eap 5 c++ move-semantics return-value-optimization c++11
假设我想实现一个应该处理对象并返回一个新的(可能已更改的)对象的函数.我想在C + 11中尽可能高效地做到这一点.环境如下:
class Object {
/* Implementation of Object */
Object & makeChanges();
};
Run Code Online (Sandbox Code Playgroud)
我想到的替代方案是:
// First alternative:
Object process1(Object arg) { return arg.makeChanges(); }
// Second alternative:
Object process2(Object const & arg) { return Object(arg).makeChanges(); }
Object process2(Object && arg) { return std::move(arg.makeChanges()); }
// Third alternative:
Object process3(Object const & arg) {
Object retObj = arg; retObj.makeChanges(); return retObj;
}
Object process3(Object && arg) { std::move(return arg.makeChanges()); }
Run Code Online (Sandbox Code Playgroud)
注意:我想使用包装函数,process()因为它将执行一些其他工作,我希望尽可能多地重用代码.
更新:
我使用makeChanges()带有给定签名的,因为我正在处理的对象提供了具有该类型签名的方法.我猜他们用它来进行方法链接.我还修复了提到的两个语法错误.谢谢你指出这些.我还添加了第三种替代方案,我将在下面提出问题.
使用clang [ie Object obj2 = process(obj);] 尝试这些导致以下结果:
第一个选项对复制构造函数进行两次调用; 一个用于传递参数,另一个用于返回.可以改为说return std::move(..)一次调用复制构造函数和一次调用移动构造函数.我知道RVO无法摆脱其中一个调用,因为我们正在处理函数参数.
在第二个选项中,我们仍然有两次调用复制构造函数.这里我们做一个显式调用,一个是在返回时进行的.我期待RVO能够开始并摆脱后者,因为我们返回的对象是与参数不同的对象.然而,它没有发生.
在第三个选项中,我们只有一个对复制构造函数的调用,这是显式的.(N)RVO消除了我们为返回而做的复制构造函数调用.
我的问题如下:
谢谢!
How*_*ant 17
我喜欢测量,所以我设置了这个Object:
#include <iostream>
struct Object
{
Object() {}
Object(const Object&) {std::cout << "Object(const Object&)\n";}
Object(Object&&) {std::cout << "Object(Object&&)\n";}
Object& makeChanges() {return *this;}
};
Run Code Online (Sandbox Code Playgroud)
我推测一些解决方案可能会为xvalues和prvalues(两者都是rvalues)提供不同的答案.所以我决定测试它们(除了左值):
Object source() {return Object();}
int main()
{
std::cout << "process lvalue:\n\n";
Object x;
Object t = process(x);
std::cout << "\nprocess xvalue:\n\n";
Object u = process(std::move(x));
std::cout << "\nprocess prvalue:\n\n";
Object v = process(source());
}
Run Code Online (Sandbox Code Playgroud)
现在这是一个简单的事情,尝试所有的可能性,那些由他人贡献的,我自己扔了一个:
#if PROCESS == 1
Object
process(Object arg)
{
return arg.makeChanges();
}
#elif PROCESS == 2
Object
process(const Object& arg)
{
return Object(arg).makeChanges();
}
Object
process(Object&& arg)
{
return std::move(arg.makeChanges());
}
#elif PROCESS == 3
Object
process(const Object& arg)
{
Object retObj = arg;
retObj.makeChanges();
return retObj;
}
Object
process(Object&& arg)
{
return std::move(arg.makeChanges());
}
#elif PROCESS == 4
Object
process(Object arg)
{
return std::move(arg.makeChanges());
}
#elif PROCESS == 5
Object
process(Object arg)
{
arg.makeChanges();
return arg;
}
#endif
Run Code Online (Sandbox Code Playgroud)
下表总结了我的结果(使用clang -std = c ++ 11).第一个数字是复制结构的数量,第二个数字是移动结构的数量:
+----+--------+--------+---------+
| | lvalue | xvalue | prvalue | legend: copies/moves
+----+--------+--------+---------+
| p1 | 2/0 | 1/1 | 1/0 |
+----+--------+--------+---------+
| p2 | 2/0 | 0/1 | 0/1 |
+----+--------+--------+---------+
| p3 | 1/0 | 0/1 | 0/1 |
+----+--------+--------+---------+
| p4 | 1/1 | 0/2 | 0/1 |
+----+--------+--------+---------+
| p5 | 1/1 | 0/2 | 0/1 |
+----+--------+--------+---------+
Run Code Online (Sandbox Code Playgroud)
process3看起来对我来说是最好的解决方案.但是它确实需要两次重载.一个处理左值,一个处理右值.如果由于某种原因这是有问题的,解决方案4和5只需要一次重载,代价是glvalues(lvalues和xvalues)的1个额外移动构造.这是一个判断调用,是否一个人想要支付额外的移动结构来节省超载(并没有一个正确的答案).
(已回答)为什么RVO会选择最后一个选项而不是第二个?
要让RVO启动,return语句需要如下所示:
return arg;
Run Code Online (Sandbox Code Playgroud)
如果你复杂化:
return std::move(arg);
Run Code Online (Sandbox Code Playgroud)
要么:
return arg.makeChanges();
Run Code Online (Sandbox Code Playgroud)
然后RVO被禁止.
有一个更好的方法吗?
我最喜欢的是p3和p5.我对p5优于p4的偏好仅仅是风格上的.我回避收拾move的return语句时,我知道它会自动生怕一不小心抑制视网膜静脉阻塞应用.但是在p5中,无论如何RVO都不是一个选项,即使return语句确实得到了隐含的移动.所以p5和p4确实是等价的.选择你的风格.
如果我们传入临时,第二和第三选项会在返回时调用移动构造函数.是否有可能消除使用(N)RVO?
"prvalue"列与"xvalue"列对应此问题.一些解决方案为xvalues添加了额外的移动构造,而有些则没有.