为什么Std::Move会阻止RVO(返回值优化)?
在许多情况下,从函数返回LOCAL时,会使用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);
}
移动函数始终被调用。因此,试图让它变得安全只会让它变得更糟。
我的问题是:
- 为什么
std::move
阻止RVO? - 什么时候抱最好的希望并依靠RVO更好,什么时候应该明确使用
std::move
?或者,换句话说,如果没有应用RVO,我如何让编译器优化完成它的工作,同时仍然强制执行Move?
解决方案
允许复制和移动省略的情况参见标准(版本N3690)第12.8§31节:
当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用。在这种情况下,实现将被省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在这两个对象在没有优化的情况下将被销毁的较晚时间。这种复制/移动操作的省略称为复制省略,在以下情况下是允许的(可以组合在一起以消除多个副本):
- 在具有类返回类型的函数的
return
语句中,当表达式是非易失性自动对象(函数或Catch-子句参数除外)的名称时,如果该非易失性自动对象的cv未限定类型与函数返回类型相同,则可以通过将自动对象直接构造到函数的返回值中来省略复制/移动操作
- [...]
- 当尚未绑定到引用(12.2)的临时类对象将被复制/移动到具有相同cv非限定类型的类对象时,可以通过将临时对象直接构造到省略的复制/移动的目标中来省略复制/移动操作
- [...]
(我省略的两种情况指的是抛出和捕获异常对象的情况,我认为这对优化不太重要。)
因此,在RETURN语句中,仅当表达式是局部变量的名称时才会发生复制省略。如果您写入std::move(var)
,则它不再是变量的名称。因此,如果移动应符合标准,则编译器无法省略该移动。
Stephan T.Lavej在Going Native 2013(Alternative source)上谈到了这一点,并在这里准确地解释了您的情况以及避免std::move()
的原因。38点04分开始收看。基本上,当返回返回类型的局部变量时,它通常被视为右值,因此默认情况下启用Move。
相关文章