为什么std :: move阻止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,调试和发布(std::move)配置测试了此代码。 永远不会调用复制ctor。 仅在调试配置中,VC ++ 11才调用move ctor。 实际上,特别是对于这些编译器而言,一切似乎都很好,但是据我所知,RVO是可选的。

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

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return std::move(heavy);
}

总是调用move ctor。 因此,试图使其“安全”会使情况变得更糟。

我的问题是:
-为什么std::move会阻止RVO?
-什么时候最好“依靠最好”并依靠RVO,何时应该明确使用std::move? 或者换句话说,如果不应用RVO,如何让编译器优化工作并仍然强制执行移动?

cdoubleplusgood asked 2020-02-13T21:26:46Z
2个解决方案
36 votes

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

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

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

(我遗漏的两种情况是指抛出和捕获异常对象的情况,我认为它们对于优化不太重要。)

因此,在return语句中,仅当表达式是局部变量的名称时才可以进行复制省略。 如果编写std::move(),则它不再是变量的名称。 因此,如果编译器应符合标准,则该编译器无法忽略该移动。

斯蒂芬·拉瓦维(Stephan T.Lavavej)在2013年的“土著人”中谈到了这一点,并确切解释了您的处境以及为什么在这里避免std::move()。 在38:04分钟开始观看。 基本上,当返回返回类型的局部变量时,通常将其视为右值,因此默认情况下启用移动。

Ralph Tandetzky answered 2020-02-13T21:27:39Z
16 votes

如果不应用RVO,如何让编译器优化工作并仍然强制执行移动?

像这样:

HeavyWeight MakeHeavy()
{
    HeavyWeight heavy;
    return heavy;
}

必须将回报转化为举动。

R. Martinho Fernandes answered 2020-02-13T21:28:08Z
translate from https://stackoverflow.com:/questions/19267408/why-does-stdmove-prevent-rvo