在没有返回值优化的情况下将两个对象添加到一起时,会创建多少个临时对象?

时间:2018-01-24 14:15:23

标签: c++ operator-keyword copy-elision return-value-optimization effective-c++

我在阅读了本书第20和22页的更多有效C ++"作者:Scott Meyers。

让我们说你写了一个代表有理数的类:

class Rational
{
public:
    Rational(int numerator = 0, int denominator = 1);

    int numerator() const;
    int denominator() const;

    Rational& operator+=(const Rational& rhs); // Does not create any temporary objects
    ...
};

现在让我们说您决定使用operator+实施operator+=

const Rational operator+(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs) += rhs;
}

我的问题是:如果返回值优化被禁用,operator+会创建多少个临时变量?

Rational result, a, b;
...
result = a + b;

我相信会创建2个临时值:一个在Rational(lhs)体内执行operator+时,另一个由operator+返回的值是通过复制第一个临时值创建的。 / p>

当斯科特提出这项行动时,我的困惑出现了:

Rational result, a, b, c, d;
...
result = a + b + c + d;

并写道:"可能使用3个临时对象,每个对operator+"的一次调用。我相信如果返回值优化被禁用,上面的操作将使用6个临时对象(每次调用operator+时为2),而如果启用,则上面的操作将使用没有临时性的。斯科特是如何得出他的结果的?我认为唯一的方法是部分应用返回值优化

4 个答案:

答案 0 :(得分:3)

我认为你只是考虑得太多,尤其是优化的细节。

对于result = a + b + c + d;,作者只想声明将创建3个临时值,第一个用于a + b的结果,然后第二个用于temporary#1 + c的结果,第3个用于temporary#2 + d,然后将其分配给result。在那之后,3个临时工被摧毁。所有临时工作仅用作中间结果。

另一方面,像expression templates这样的习语可以直接通过消除临时性来获得最终结果。

答案 1 :(得分:1)

编译器可以检测累积并应用优化,但通常从左到右移位和减少表达式在某种程度上是棘手的,因为它可能会受到样式a + b * c * d的表达式的影响

采取以下形式更为谨慎:

  

a +(b +(c + d))

在优先级较高的运营商可能需要之前不会使用变量。但是评估它需要时间。

答案 2 :(得分:1)

编译器不会创建任何变量。因为变量是出现在源代码中的变量,变量不会在执行时存在,或者在可执行文件中存在(它们可能会成为内存位置,或者被忽略" )。

了解as-if rule。编译器通常是optimizing

参见CppCon 2017 Matt Godbolt “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” talk

答案 3 :(得分:1)

在表达式a+b+c+d中,将创建和销毁6个临时值,这是必需的(有和没有RVO)。您可以查看here

operator +内部定义中,在Rational(lhs)+=a表达式中,prvalue Rational(lhs)将绑定到operator+=隐含对象参数。根据这个非常具体的规则[over.match.func]/5.1授权(在[expr.call]/4中提到)

  

即使隐式对象参数不是const限定的,只要在所有其他方面都可以将参数转换为隐式对象参数的类型,就可以将rvalue绑定到参数。

然后,要将prvalue绑定到引用,必须进行临时实现[class.temporary]/2.1

  

临时对象已实现[...]:

     
      
  • 将引用绑定到prvalue时
  •   

因此,在执行每个operator +电话时会创建一个临时表。

然后返回的表达式Rational(lhs)+=a可以 conceptualy 看作Rational(Rational(lhs)+=a)是prvalue(prvalue表达式,其评估初始化一个对象 - phi:一个权力对象),然后绑定到对operator +的两个后续调用的第一个参数。引用的规则[class.temporary] /2.1再次应用两次,将创建2个临时值:

  1. 一个用于实现a+b
  2. 的结果
  3. 另一个用于实现(a+b)+c
  4. 的结果

    所以在这一点上已经创造了4个临时工。然后,对operator+的第三次调用在函数体内创建了第5个临时值

    最后一次调用operator +的结果是丢弃的值表达式。该标准的最后一条规则适用于[class.temporary] /2.6:

      

    临时对象已实现[...]:

         
        
    • 当prvalue显示为丢弃值表达式时。
    •   

    产生第6个临时工。

    如果没有RVO,则直接实现返回值,这使得不再需要临时实现返回值。这就是为什么GCC使用和不使用-fno-elide-constructors编译器选项生成完全相同的程序集。

    为了避免临时实现,您可以定义operator +

    const Rational operator+(Rational lhs, const Rational& rhs)
    {
        return lhs += rhs;
    }
    

    通过这样的定义,prvalue a+b(a+b)+c将直接用于初始化为operator +的第一个参数,这将使您免于实现2个临时值。查看程序集here