我在阅读了本书第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),而如果启用,则上面的操作将使用没有临时性的。斯科特是如何得出他的结果的?我认为唯一的方法是部分应用返回值优化。
答案 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个临时值:
a+b
,(a+b)+c
所以在这一点上已经创造了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。