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

cdo*_*ood 51 c++ move-semantics rvo c++11

在许多情况下,从函数返回局部时,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;
}
Run Code Online (Sandbox Code Playgroud)

我用VC++ 11和GCC 4.71,debug和release(-O2)配置测试了这段代码.永远不会调用复制文件.移动ctor仅在调试配置中由VC++ 11调用.实际上,特别是这些编译器似乎都很好,但据我所知,RVO是可选的.

但是,如果我明确使用move:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return std::move(heavy);
}
Run Code Online (Sandbox Code Playgroud)

移动ctor总是被称为.因此,试图使其"安全"会使情况变得更糟.

我的问题是:
- 为什么要std::move预防RVO?
- 何时更好地"希望最好"并依赖RVO,何时应该明确使用std::move?或者,换句话说,如果不应用RVO,如何让编译器优化完成其工作并仍然强制移动?

Ral*_*zky 37

允许复制和移动省略的情况见标准(版本N3690)第12.8§31节:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使为复制/移动操作选择的构造函数和/或对象的析构函数具有副作用.在这种情况下,实现将省略的复制/移动操作的源和目标视为仅仅两种不同的引用同一对象的方式,并且该对象的销毁发生在两个对象的后期时间.没有优化就被破坏了.复制/移动操作的省略,称为复制省略,在以下情况下允许(可以合并以消除多个副本):

  • return具有类返回类型的函数的语句中,当表达式是具有与函数返回类型相同的cv-unqualified类型的非易失性自动对象(除函数或catch子句参数之外)的名称时,通过将自动对象直接构造到函数的返回值中,可以省略复制/移动操作
  • [...]
  • 当一个未绑定到引用(12.2)的临时类对象被复制/移动到具有相同cv-nonqualified类型的类对象时,可以通过将临时对象直接构造到目标中来省略复制/移动操作省略的复制/移动
  • [...]

(我遗漏的两个案例是指抛出和捕获异常对象的情况,我认为这对于优化不太重要.)

因此,在返回语句中,如果表达式是局部变量的名称,则只能发生复制省略.如果你写std::move(var),那么它不再是变量的名称.因此编译器不能忽略移动,如果它应该符合标准.

Stephan T. Lavavej在2013年的Going Native上谈到了这一点,并准确地解释了你的情况以及为什么要避免std::move()这里.在38:04分钟开始观看.基本上,当返回返回类型的局部变量时,它通常被视为右值,因此默认情况下启用移动.

  • 我们应该修复,以便可以省略`return std :: move`.如果我们可以告诉C++函数的引用返回值保证与函数的一个特定引用输入值相同,那么可能会打开一些有趣的结果.(从非平凡表达式中删除,临时输入参数的生命周期扩展到函数而不返回临时,为两个:第二个是恕我直言更重要). (5认同)
  • 是的,我不明白无法消除这一点的原因。只是更难做到,让编译器编写者有更多时间? (2认同)

R. *_*des 16

如果不应用RVO,我怎样才能让编译器优化完成其工作并仍然强制移动?

像这样:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return heavy;
}
Run Code Online (Sandbox Code Playgroud)

将返回转换为移动是强制性的.

  • @cdoubleplusgood差不多.直接返回本地非参数保证在最坏的情况下"移动".像`返回条件一样无害的东西?heavy1:heavy2;`可能会阻止隐式`move`,而`if(condition)返回heavy1; 返回heavy2;`不会.它有点脆弱. (8认同)
  • 这实际上是一个有用的警告. (6认同)
  • 我想我需要为`return std :: move`做一些grep'ping;) (2认同)