必须返回对象时,别妄想返回其reference——条款21

        一旦程序员领悟了pass-by-value(传值)的效率牵连层面(见条款20),往往变成十字军战士,一心一意根除pass-by-value带来的种种邪恶。在坚定追求pass-by-reference的纯度中,他们一定会犯下一个致命错误:开始传递一些references指向其实并不存在的对象。这可不是件好事。

        考虑一个用来表现有理数(rational numbers)的class,内含一个函数用来计算两个有理数的乘积:

class Rational {
public:
	Rational(int numeratior = 0, int denominator = 1);
	...
private:
	int n, d;
	friend const Rational operator* (const Rational& lhs, const Rational& rhs);
};

        这个版本的operator*系以by value方式返回其计算结果(一个对象)。如果你完全不担心该对象的构造和析构成本,你其实是明显逃避了你的专业责任。若非必要,没有人会想要为这样的对象付出太多代价,问题是需要付出任何代价吗?

        以上述operator*为例,如果它返回一个reference,后者一定指向某个既有的Rational对象,内含两个Rational对象的乘积。

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

const Rational& operator* (const Rational& lhs, const Rational& rhs) {
	Rational result(lhs.n * rhs.n, lhs.d * rhs.d);  // 警告!糟糕的代码!
	return result;
}

        你可以拒绝这种做法,因为你的目标是要避免构造函数,而result却必须像任何对象一样地由构造函数构造起来。更严重的是:这个函数返回一个reference指向result,但result是个local对象,而local对象在函数退出前被销毁了。任何调用者只要对此函数返回值做任何一点点运用,就会面临“无定义行为”的问题。

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

const Rational& operator* (const Rational& lhs, const Rational& rhs) {
	Rational* result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);  // 警告!更糟的写法
	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*返回的结果调用构造函数而受惩罚。也许你还记得我们最初的目标是要避免如此的构造函数调用动作。获取有一种办法可以避免任何构造函数被调用。

const Rational& operator* (const Rational& lhs, const Rational& rhs) 
{                               // 警告!更糟的写法
	static Rational result;     // static对象,此函数将返回其reference。
	result = ...;               // 将lhs乘以rhs,并将结果置于result内。
	return result;
}

        就像所有用上static对象的设计意义,这一个也立刻造成我们对多线程安全性的疑虑。不过那还只是显而易见的弱点。更深层的瑕疵,考虑以下面这些完全合理的客户代码:

bool operator==(const Rational& lhs, const Rational& rhs);  // 一个针对Rational而写的operator==
Rational a, b, c, d;
...
if ((a * b) == (c * d)) {
	当乘积相等时,做适当相应动作;
}
else {
	当乘积不相等时,做适当相应动作;
}

        上述表达式((a * b) == (c * d))总被核算为true,不论a, b, c和d的值是什么!

        一旦将代码重新写为等价的函数形式,很容易就可以了解出了什么意外:

if (operator==(operator*(a, b), operator*(c, d)))

        注意,在operator==被调用前,已有两个operator* 调用式起作用,每一个都返回reference指向operator*内部定义的static Rational对象。因此每次都为true。

        因此,一个“必须返回新对象”的函数的正确写法是:就让那个函数返回一个新对象呗。对Rational的operator*而言意味以下写法(或其它本质上等价的代码):

inline const Rational operator* (const Rational& lhs, const Rational& rhs) 
{                               
	return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

        当然,你需得承受operator*返回值的构造成本和析构成本,然而长远来看那只是为了获得正确行为而付出的一个小小代价。

        综上所述,当你必须在“返回一个reference和返回一个object”之间抉择时,你的工作就是挑出行为正确的那个。

请记住

  • 绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值