禁用返回值优化时,Rational类operator+的临时对象数量疑问
首先,我们来拆解你的问题核心:为什么禁用返回值优化(RVO)时,你认为a + b + c + d会产生6个临时对象,但Scott Meyers却指出“可能会使用3个”?这背后的关键在于编译器对临时对象的消除规则,以及你对RVO禁用场景的假设差异。
1. 单个a + b的临时对象分析
先看你提到的operator+实现:
const Rational operator+(const Rational& lhs, const Rational& rhs) { return Rational(lhs) += rhs; }
你认为这里会创建2个临时对象:
- 第一个:
Rational(lhs)构造的匿名临时对象(记为T1) - 第二个:将T1复制到
operator+返回值的临时对象(记为T2)
这个分析在完全禁用所有复制消除的极端场景下是对的,但实际编译器的行为并非如此:return Rational(lhs) += rhs;中的Rational(lhs)是一个匿名临时,而operator+=返回的是它的引用。编译器可以识别到这个引用指向的是一个即将被返回的临时对象,因此会直接将Rational(lhs)构造在operator+返回值的内存位置上——这属于RVO的一种特殊情况,很多编译器即使在禁用命名返回值优化(NRVO)的选项下,仍然会对匿名临时的返回做这种优化。
在这种优化下,operator+只会创建1个临时对象(就是返回值本身),而不是2个。
2. a + b + c + d的临时对象计数
表达式a + b + c + d会被解析为((a + b) + c) + d,对应3次operator+调用:
- 第一次调用
operator+(a, b):产生1个临时对象(记为T1,即a+b的结果) - 第二次调用
operator+(T1, c):产生1个临时对象(记为T2,即(a+b)+c的结果) - 第三次调用
operator+(T2, d):产生1个临时对象(记为T3,即最终结果)
总共3个临时对象,这就是Scott Meyers所说的“每个operator+调用对应一个”的情况。
而你假设的6个临时对象,只有在编译器完全不做任何复制消除的情况下才会发生——但这种情况在现实中几乎不存在,因为C++标准明确允许编译器消除这种不必要的临时对象复制,无论是否开启显式的RVO选项。
3. 关于你对RVO启用场景的误解
你提到“若启用返回值优化,则不会创建任何临时对象”,这是不准确的:
即使启用RVO,a + b的返回值仍然是一个临时对象(比如赋值给result时,这个临时对象会被移动或复制到result中)。RVO只是消除了operator+内部的临时对象复制,而不是消除返回值本身的临时对象。
总结一下:Scott的结论基于编译器对匿名临时返回的常规优化行为,而你的分析是基于极端的“完全禁用所有复制消除”场景——这就是两者差异的根源。
内容的提问来源于stack exchange,提问作者user3266738

