More Effective C++ 20:协助完成返回值优化

一个返回对象的函数很难有较高的效率,因为传值返回会导致调用对象内的构造和析构函数,这种调用是不能避免的。问题很简单:一个函数要么为了保证正确的行为而返回对象要么就不这么做。如果它返回了对象,就没有办法摆脱被返回的对象。

考虑以下类:

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 位置(或者在函数被调用位置用一个对象来替代)来消除局部临时对象――是众所周知的和被普遍实现的。它甚至还有一个名字:返回值优化

但注意,这种优化对普通的赋值运算无效,编译器不能够用拷贝构造函数取代赋值运算动作。

猜你喜欢

转载自blog.csdn.net/qq_44800780/article/details/106768259