Effective C++ 条款21_必须返回对象时,别妄想返回其reference_不止于此

Don’t try to return a reference when you must return an object

考虑:开始传递一些 references 指向其实并不存在的对象,这可不是好事。
e,g,
下面写一个带有两有理数(rational numbers)相乘功能的 class,

class Rational {
public:
	Rational(int numerator = 0, int denominator = 1);
	...
private:
	int a, b;
	friend const Rational operator* (const Rational& l, const Rational& r);
};

如果可以改为传递 reference,就不需要付出代价。但是记住,所谓 reference 只是一个名称,代表某个既有的对象。任何时候看到一个 reference 声明式,你都应该立刻问自己,它代表的是哪个对象?因为它一定是某物的另一个名称。以上述 operator* 为例,如果它返回一个 reference,后者一定指向某个既有的 Rational 对象,内含两个 Rational 对象的乘积。

我们当然不可能期望这样一个(内含乘积的)Rational 对象在调用 operator* 之前就存在。也就是说,如果有:

Rational a(2, 1);			// a = 2/1
Rational b(3, 4);			// b = 3/4
Rational c = a * b;			//c 应该是 3/2

期望 “ 原本就存在一个值为 3/2 的 Rational 对象 ” 并不合理。如果 operator* 要返回一个 reference 指向如此数值,它必须自己创建那个 Rational 对象。

函数创建新对象的途径有二:在 stack 空间或在 heap 空间创建之。如果定义一个 local 变量,就是在 stack 空间创建对象。根据这个策略写下 operator* 如下:

const Rational& operator* (const Rational& l, const Rational& r){
		Rational result(l.a * r.a, r.a * r.b); 			// 警告!糟糕的代码!
		return result;
}

你可以拒绝这种做法,因为你的目标是要避免调用构造函数,而 result 却必须像任何对象一样由构造函数构造出来。更严重的是:
这个函数返回一个 reference 指向 result,但 result 是个 local 对象,而 local 对象在函数退出前就被销毁了。
真相是,任何函数如果返回一个 reference 指向某个 local 对象,都将一败涂地。(如果函数返回指针指向一个 local 对象,也是一样)

于是,我们考虑在 heap 内构造一个对象,并返回 reference 指向它。Heap-based 对象由 new 创建,所以你得写一个 heap-based operator* 如下:

const Rational& operator* (const Rational& l, const Rational& r){
		Rational* result = new Rational(l.a * l.b, r.a * r.b);		// 警告!更糟糕的写法!
		return *result;
}

唔,你还是必须得付出一个 “ 构造函数调用 ” 的代价,因为分配所得到的内存将以一个适当的构造函数完成初始化操作。但此外你现在又有了另一个问题:谁该对着被你 new 出来的对象实施 delete?

即使调用者诚实谨慎,并且出于良好意识,他们还是不太能够在这样合情合理的用法下阻止内存泄露:

Rational w, x, y, z;
w = x * y * z;			// 与 operator*(operator*(x, y), z) 相同

这里,同一个语句内调用了两次 operator*,因而两次使用 new,也就需要两次 delete。但却没有合理的办法让 operator* 使用者进行那些 delete 的调用,因为没有合理的办法让他们取得 operator* 返回的references 背后隐藏的那个指针。这绝对导致资源泄露

或许你已经注意到了,上述不论 on-the-stack 或 on-the-heap 做法,都因为对 operator* 返回的结果调用构造函数而受到惩罚。
或许你想到了另一种方式,让 operator* 返回的 reference 指向一个被定义于函数内部的 static Rational 对象。但这会立刻造成我们对多线程安全性的疑虑。不多赘述,反正这样做很不好。

建议:一个 “ 必须返回新对象 ” 的函数的正确写法是:就让那个函数返回一个新对象呗。
对 Rational 的 operator* 而言意味以下写法:

inline const Rational operator* (const Rational& l, const Rational& r){
		return Rational(l.a * l.b, r.a * r.b);
}

当然,你需要承受 operator* 返回值的构造成本和析构成本,然而长远来看那只是为了获得正确行为而付出的一个小小代价。
总结:当你必须在 “ 返回一个 reference 和返回一个 object ” 之间抉择时,你的工作就是挑出行为正确的那个。

请记住:
绝不要返回 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static 对象而有可能同时需要多个这样的对象。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值