一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数,这种调用是不能避免的。问题很简单:一个函数要么为了保证正确的行为而返回对象要么就不这么做。如果它返回了对象,就没有办法摆脱被返回的对象。
考虑以下类:
class Rational
{
public:
Rational(int numerator = 0, int denominator = 1);
...
int numerator() const;
int denominator() const;
};
const Rational operator*(const Rational& lhs, const Rational& rhs);
不用看 operator*
的代码,我们就知道它肯定要返回一个对象,因为它返回的是两个任意数字的计算结果。这些结果是任意的数字。operator*
如何能避免建立新对象来容纳它们的计算结果呢?这是不可能的,所以它必须得建立新对象并返回它。
从效率的观点来看,你不应该关心函数返回的对象,你仅仅应该关心对象的开销。你所应该关心的是把你的努力引导到寻找减少返回对象的开销上来,而不是去消除对象本身。
以某种方法返回对象,能让编译器消除临时对象的开销,这样编写函数通常是很普遍的。这种技巧是返回 constructor argument
而不是直接返回对象,你可以这样做:
// 一种高效和正确的方法,用来实现
// 返回对象的函数
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
仔细观察被返回的表达式,它正在调用 Rational
的构造函数,你通过这个表达式建立一个临时的 Rational
对象,
Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
并且这是一个临时对象,函数把它拷贝给函数的返回值。
返回 constructor argument
而不出现局部对象,这种方法还会给你带来很多开销,因为你仍旧必须为在函数内临时对象的构造和释放而付出代价,你仍旧必须为函数返回对象的
构造和释放而付出代价。但是你已经获得了好处。
C++规则允许编译器优化不出现的临时对象。因此如果你在如下的环境里调用 operator*
:
Rational a = 10;
Rational b(1, 2);
Rational c = a * b;
编译器就会被允许消除在 operator*
内的临时变量和 operator*
返回的临时变量。
能在为目标 c 分配的内存里构造 return 表达式定义的对象。如果你的编译器这样去做,调用 operator*
的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函数――建立 c 时调用的构造函数。而且你不能比这做得更好了,因为 c 是命名对象,命名对象不能被消除。
你还可以通过把函数声明为 inline
来消除 operator*
的调用开销。
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator() * rhs.denominator());
}
这种特殊的优化――通过使用函数的 return
位置(或者在函数被调用位置用一个对象来替代)来消除局部临时对象――是众所周知的和被普遍实现的。它甚至还有一个名字:返回值优化
但注意,这种优化对普通的赋值运算无效,编译器不能够用拷贝构造函数取代赋值运算动作。