在没有 return 值优化的情况下将两个对象加在一起时会创建多少个临时对象?
How many temporary objects are created when two objects are added together without the return value optimization?
在阅读 Scott Meyers 的书 "More Effective C++" 的第 20 和 22 条后,我决定问这个问题。
假设您写了一个 class 来表示有理数:
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;
}
我的问题是:如果 return 值优化 被禁用,operator+
会创建多少临时变量?
Rational result, a, b;
...
result = a + b;
我相信创建了 2 个临时对象:一个是在 Rational(lhs)
在 operator+
的主体内执行时,另一个是在创建由 operator+
编辑的值 return 时通过复制第一个临时文件。
当 Scott 介绍这个操作时,我感到困惑:
Rational result, a, b, c, d;
...
result = a + b + c + d;
并写道:"Probably uses 3 temporary objects, one for each call to operator+
"。我相信如果 return 值优化 被禁用,上面的操作将使用 6 个临时对象(每次调用 operator+
2 个),而如果它是启用后,上面的操作将完全不使用临时对象。斯科特是如何得出他的结果的?我认为这样做的唯一方法是部分应用 return 值优化.
我觉得你考虑的太多了,尤其是优化的细节。
对于result = a + b + c + d;
,作者只是想声明将创建3个临时文件,第一个是针对a + b
的结果,然后第二个是针对[=12的结果=],第三个是给temporary#2 + d
赋值给result
的。之后,3 个临时物被销毁。所有临时值仅用作中间结果。
另一方面,像expression templates这样的一些成语可以通过消除临时项直接得到最终结果。
编译器可能会检测累加并应用优化,但通常从左到右移动和减少表达式在某种程度上是棘手的,因为它可能会被样式 a + b * c * d
的表达式击中
形式比较谨慎:
a + (b + (c + d))
在更高优先级的运算符可能需要变量之前,它不会使用变量。但评估它需要临时工。
编译器没有创建任何变量。因为变量是那些出现在源代码中的变量,并且 变量在执行时 或可执行文件中不存在(它们可能成为内存位置,或者 "ignored")。
了解 as-if rule. Compilers are often optimizing。
参见 CppCon 2017 Matt Godbolt “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” talk。
在表达式 a+b+c+d
中将创建和销毁 6 个临时对象,这是强制性的(有和没有 RVO)。你可以查看一下here.
在 operator +
定义中,在表达式 Rational(lhs)+=a
中,纯右值 Rational(lhs)
将绑定到 隐含对象参数 operator+=
根据这个非常具体的规则授权 [over.match.func]/5.1 (refered in [expr.call]/4)
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
然后要将纯右值绑定到引用,必须发生临时物化 [class.temporary]/2.1
Temporary objects are materialized [...]:
- when binding a reference to a prvalue
因此在每个 operator +
调用的执行过程中都会创建一个临时文件。
那么表达式 Rational(lhs)+=a
曾经被 returned 可以 概念上 视为 Rational(Rational(lhs)+=a)
是纯右值(a prvalue 是一个 表达式,其评估初始化一个对象 - phi:an 对象),然后绑定到对 operator +
的 2 个后续调用的第一个参数。引用的规则 [class.temporary]/2.1 再次应用两次并将创建 2 个临时对象:
- 一个用于实现
a+b
、 的结果
- 另一个实现
(a+b)+c
的结果
所以此时已经创建了 4 个临时文件。然后,第三次调用 operator+
在函数体内创建第 5 个临时变量
最后一次调用 operator +
的结果是 丢弃值表达式 。该标准的最后一条规则适用于 [class.temporary]/2.6:
Temporary objects are materialized [...]:
- when a prvalue appears as a discarded-value expression.
产生第 6 个临时变量。
如果没有 RVO,return 值将直接具体化,这使得 return 纯右值的临时具体化不再是必要的。这就是为什么 GCC 在使用和不使用 -fno-elide-constructors
编译器选项的情况下生成完全相同的程序集的原因。
为了避免临时实体化,你可以定义operator +
:
const Rational operator+(Rational lhs, const Rational& rhs)
{
return lhs += rhs;
}
有了这样的定义,纯右值 a+b
和 (a+b)+c
将直接用于初始化为 operator +
的第一个参数,这将使您免于实现 2 个临时对象。请参阅程序集 here。
在阅读 Scott Meyers 的书 "More Effective C++" 的第 20 和 22 条后,我决定问这个问题。
假设您写了一个 class 来表示有理数:
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;
}
我的问题是:如果 return 值优化 被禁用,operator+
会创建多少临时变量?
Rational result, a, b;
...
result = a + b;
我相信创建了 2 个临时对象:一个是在 Rational(lhs)
在 operator+
的主体内执行时,另一个是在创建由 operator+
编辑的值 return 时通过复制第一个临时文件。
当 Scott 介绍这个操作时,我感到困惑:
Rational result, a, b, c, d;
...
result = a + b + c + d;
并写道:"Probably uses 3 temporary objects, one for each call to operator+
"。我相信如果 return 值优化 被禁用,上面的操作将使用 6 个临时对象(每次调用 operator+
2 个),而如果它是启用后,上面的操作将完全不使用临时对象。斯科特是如何得出他的结果的?我认为这样做的唯一方法是部分应用 return 值优化.
我觉得你考虑的太多了,尤其是优化的细节。
对于result = a + b + c + d;
,作者只是想声明将创建3个临时文件,第一个是针对a + b
的结果,然后第二个是针对[=12的结果=],第三个是给temporary#2 + d
赋值给result
的。之后,3 个临时物被销毁。所有临时值仅用作中间结果。
另一方面,像expression templates这样的一些成语可以通过消除临时项直接得到最终结果。
编译器可能会检测累加并应用优化,但通常从左到右移动和减少表达式在某种程度上是棘手的,因为它可能会被样式 a + b * c * d
的表达式击中形式比较谨慎:
a + (b + (c + d))
在更高优先级的运算符可能需要变量之前,它不会使用变量。但评估它需要临时工。
编译器没有创建任何变量。因为变量是那些出现在源代码中的变量,并且 变量在执行时 或可执行文件中不存在(它们可能成为内存位置,或者 "ignored")。
了解 as-if rule. Compilers are often optimizing。
参见 CppCon 2017 Matt Godbolt “What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid” talk。
在表达式 a+b+c+d
中将创建和销毁 6 个临时对象,这是强制性的(有和没有 RVO)。你可以查看一下here.
在 operator +
定义中,在表达式 Rational(lhs)+=a
中,纯右值 Rational(lhs)
将绑定到 隐含对象参数 operator+=
根据这个非常具体的规则授权 [over.match.func]/5.1 (refered in [expr.call]/4)
even if the implicit object parameter is not const-qualified, an rvalue can be bound to the parameter as long as in all other respects the argument can be converted to the type of the implicit object parameter.
然后要将纯右值绑定到引用,必须发生临时物化 [class.temporary]/2.1
Temporary objects are materialized [...]:
- when binding a reference to a prvalue
因此在每个 operator +
调用的执行过程中都会创建一个临时文件。
那么表达式 Rational(lhs)+=a
曾经被 returned 可以 概念上 视为 Rational(Rational(lhs)+=a)
是纯右值(a prvalue 是一个 表达式,其评估初始化一个对象 - phi:an 对象),然后绑定到对 operator +
的 2 个后续调用的第一个参数。引用的规则 [class.temporary]/2.1 再次应用两次并将创建 2 个临时对象:
- 一个用于实现
a+b
、 的结果
- 另一个实现
(a+b)+c
的结果
所以此时已经创建了 4 个临时文件。然后,第三次调用 operator+
在函数体内创建第 5 个临时变量
最后一次调用 operator +
的结果是 丢弃值表达式 。该标准的最后一条规则适用于 [class.temporary]/2.6:
Temporary objects are materialized [...]:
- when a prvalue appears as a discarded-value expression.
产生第 6 个临时变量。
如果没有 RVO,return 值将直接具体化,这使得 return 纯右值的临时具体化不再是必要的。这就是为什么 GCC 在使用和不使用 -fno-elide-constructors
编译器选项的情况下生成完全相同的程序集的原因。
为了避免临时实体化,你可以定义operator +
:
const Rational operator+(Rational lhs, const Rational& rhs)
{
return lhs += rhs;
}
有了这样的定义,纯右值 a+b
和 (a+b)+c
将直接用于初始化为 operator +
的第一个参数,这将使您免于实现 2 个临时对象。请参阅程序集 here。