为什么 std::move 会阻止 RVO(返回值优化)

Why does std::move prevent RVO (return value optimization)?

本文关键字:返回值 优化 RVO std move 为什么      更新时间:2023-10-16

在许多情况下,当从函数返回局部时,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。移动 ctor 仅由 VC++11 在调试配置中调用。实际上,这些编译器似乎一切都很好,但据我所知,RVO 是可选的。

但是,如果我明确使用move

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

始终调用移动 CTOR。因此,试图使其"安全"会使情况变得更糟。

我的问题是:

  • 为什么std::move可以预防房源房转机?
  • 什么时候
  • "希望最好"并依靠RVO更好,什么时候应该明确使用std::move?或者,换句话说,如果不应用 RVO,我如何让编译器优化完成其工作并仍然强制执行移动?

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

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

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

(我省略的两种情况是指抛出和捕获异常对象的情况,我认为这些异常对象对优化不太重要。

因此,在 return 语句中,仅当表达式是局部变量的名称时,才会发生复制省略。如果你写std::move(var),那么它就不再是变量的名称了。因此,如果编译器符合标准,则无法避免移动。

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

如何让编译器优化完成其工作,并在未应用 RVO 的情况下仍强制移动?

喜欢这个:

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

将回报转化为移动是强制性的。