在许多情况下,从函数返回本地时,RVO会启动。但是,我认为明确使用std::move
至少会在RVO未发生时强制移动,但RVO仍然会在可能的情况下应用。但是,似乎情况并非如此。
#include "iostream"
class HeavyWeight
{
public:
HeavyWeight()
{
std::cout << "ctor" << std::endl;
}
HeavyWeight(const HeavyWeight& other)
{
std::cout << "copy" << std::endl;
}
HeavyWeight(HeavyWeight&& other)
{
std::cout << "move" << std::endl;
}
};
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return heavy;
}
int main()
{
auto heavy = MakeHeavy();
return 0;
}
我用VC ++ 11和GCC 4.71,调试和发布(-O2
)配置测试了这段代码。永远不会调用复制文件。移动ctor仅在调试配置中由VC ++ 11调用。实际上,特别是这些编译器似乎都很好,但据我所知,RVO是可选的。
但是,如果我明确使用move
:
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return std::move(heavy);
}
移动ctor总是被称为。所以试图让它“安全”会让事情变得更糟。
我的问题是:
- 为什么std::move
会阻止RVO?
- 何时更好地“希望最好”并依赖RVO,何时应明确使用std::move
?或者,换句话说,如果不应用RVO,我怎样才能让编译器优化完成其工作并仍然强制移动?
答案 0 :(得分:32)
允许复制和移动省略的情况见标准(版本N3690)第12.8§31节:
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间。没有优化就被破坏了。复制/移动操作的省略,称为复制省略,在以下情况下允许(可以合并以消除多个副本):
- 在具有类返回类型的函数中的
return
语句中,当表达式是具有相同cv-unqualified的非易失性自动对象(除函数或catch子句参数之外)的名称时键入函数返回类型,通过将自动对象直接构造为函数的返回值,可以省略复制/移动操作- [...]
- 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过直接构造临时对象来省略复制/移动操作进入省略的复制/移动的目标
- [...]
(我遗漏的两个案例是指抛出和捕获异常对象的情况,我认为这对于优化不太重要。)
因此,在返回语句中,只有当表达式是局部变量的名称时,才能发生复制省略。如果你写std::move(var)
,那么它不是变量的名称了。因此编译器不能忽略移动,如果它应该符合标准。
Stephan T. Lavavej在Going Native 2013谈到了这个问题,并详细解释了你的情况以及为什么要在这里避免std::move()
。在38:04分钟开始观看。基本上,当返回返回类型的局部变量时,它通常被视为右值,因此默认情况下启用移动。
答案 1 :(得分:15)
如果不应用RVO,我怎样才能让编译器优化完成其工作并仍然强制移动?
像这样:
HeavyWeight MakeHeavy()
{
HeavyWeight heavy;
return heavy;
}
将回报转换为移动是必须的。