【问题标题】:Will C++ compiler optimize return-by-value code?C++ 编译器会优化按值返回的代码吗?
【发布时间】:2013-09-03 13:58:23
【问题描述】:

假设我使用带有 -O2 的 Visual Studio 或现代 GCC。编译器会在func() 中创建S,然后将其复制到my_result,还是使用构造函数(5, 6, 5 + 6) 创建my_result,而不创建临时S

注意: 函数func() 定义及其用法在单独的.obj 文件中!

struct S
{
    S(int _x, int _y, int _z) : x(_x), y(_y), z(_z) { }
    int x, y, z;
};

S func(int a, int b)
{
    return S(a, b, a + b);
}


/// USAGE ///

S my_result = func( 5, 6 );

【问题讨论】:

  • 你试过了吗?结果如何?
  • 这取决于编译器和优化级别以及平台和...一切。但我希望一个像样的编译器能够进行这种优化,至少在发布版本中是这样。
  • @viraptor 我什至不知道如何检查这个优化是否完成。
  • @pavelkolodin 您可以检查编译器给出的汇编器输出。对于简短的二进制文件,它非常易读。对于 gcc,这将是 -S 选项。 VS 会有自己的。

标签: c++ rvo


【解决方案1】:

现代编译器通常会优化这种操作。见return value optimization

【讨论】:

  • +1 将其称为返回值优化而不是 RVO,因此我无需单击链接即可理解它的含义。
【解决方案2】:

这是一种优化,根据定义,它几乎意味着编译器是可选的,并且由每个特定的编译器决定要做什么。你怎么能确定呢?检查生成代码的反汇编!

也就是说,大多数编译器都应该进行这种优化(返回值优化 [RVO]),因为在这种情况下它相对容易实现(没有多次返回,它是一个未命名的临时变量,因此您没有别名等)。

【讨论】:

    【解决方案3】:

    在我看来,提供的测试用例很简单,RVO 可以申请。

    【讨论】:

      【解决方案4】:

      我怀疑临时的优化了。您可以通过在构造函数和复制构造函数中放置打印语句来测试它,并查看在不同编译器设置下打印的内容。

      【讨论】:

      • @AlexFarber:复制省略几乎是“好像”规则的唯一明确例外。复制构造函数中的副作用不会阻止副本被优化。
      • @KaxDragon 关键是,一旦出现一些副作用(比如控制台上的输出),编译器就无法再优化它了。
      • @meagar:错了。复制ctor 可以优化出来,即使它有副作用。 C++ ISO 标准明确允许这样做。
      • @meagar 编译器被明确允许在某些情况下优化掉复制构造函数即使它们有副作用。
      • @meagar ... 除了复制构造函数,无论副作用如何,它都可以显式优化掉。
      【解决方案5】:

      你可以自己测试一下,因为有问题的优化有明显的差异!

      C++ 中的大多数优化形式都遵循as-if 规则,这使得它们很难被发现。但是,在少数情况下,允许省略(跳过)复制和移动构造函数,即使差异会导致可观察到的行为变化。

      在这种情况下,将以下内容添加到 S:

      struct S {
        // ...
        S( S const& o ):x(o.x), y(o.y), z(o.z) {
          std::cout << "copy ctor!\n";
        }
        S& operator=( S const& o ) {
          x=o.x;
          y=o.y;
          z=o.z;
          std::cout << "copy assign!\n";
          return *this;
        }
        S( S && o ):x(std::move(o.x)), y(std::move(o.y)), z(std::move(o.z)) {
          std::cout << "move ctor!\n";
        }
        S& operator=( S const& o ) {
          std::tie( x,y,z ) = std::tie( std::move(o.x),std::move(o.y),std::move(o.z) );
          std::cout << "move assign!\n";
          return *this;
        }
      }
      

      并运行您的代码。通过零优化,您将获得副本和/或移动。

      使用任何非平凡的优化级别,打印都会消失,因为 RVO(以及相关情况下的 NRVO)将运行,从而消除副本。 (如果您的编译器不是 C++11,请删除上面的移动构造函数——C++03 中的优化仍然允许)

      在 C++11 中,您可以通过 return {stuff} 语法显式构造返回值,而不是依赖 NRVO/RVO。

      请注意,RVO(返回值优化)和 NRVO(命名返回值优化)相对脆弱,如果您依赖它们,您都必须了解它们的工作原理、导致它们崩溃的原因以及特定编译器的任何怪癖已在其实施中(如果有)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-22
        • 2013-09-11
        • 2013-03-11
        相关资源
        最近更新 更多