为什么std :: move会阻止RVO?

时间:2013-10-09 09:00:28

标签: c++ c++11 move-semantics rvo

在许多情况下,从函数返回本地时,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,我怎样才能让编译器优化完成其工作并仍然强制移动?

2 个答案:

答案 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;
}

将回报转换为移动是必须的。