条款 23: 必须返回一个对象时不要试图返回一个引用

        一旦程序员抓住了“传值”在效率上的把柄(参见条款 22) ,他们会变得十分极端,恨不得挖出每一个隐藏在程序中的传值操作。岂不知,在他们不懈地追求纯粹的“传引用”的过程中,他们会不可避免地犯另一个严重的错误:传递一个并不存在的对象的引用。这就不是好事了。

        

class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d; //  分子和分母
friend
const Rational //  参见条款 21:为什么
operator*(const Rational& lhs, //  返回值是 const
const Rational& rhs)
};
inline const Rational operator*(const Rational& lhs,
const Rational& rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}

        很明显,这个版本的 operator*是通过传值返回对象结果,如果不去考虑对象构造和析构时的开销,你就是在逃避作为一个程序员的责任。另外一件很明显的事实是,除非确实有必要,否则谁都不愿意承担这样一个临时对象的开销。那么,问题就归结于:确实有必要吗?

        答案是,如果能返回一个引用,当然就没有必要。但请记住,引用只是一个名字,一个其它某个已经存在的对象的名字。无论何时看到一个引用的声明,就要立即问自己:它的另一个名字是什么呢?(返回引用可以看出是返回一个指针,只是指针可以自动解引用,那返回的指针指向什么是我们必须要考虑的。因为它必然还有另外一个什么名字(见条款 M1) 。operator*来说,如果函数要返回一个引用,那它返回的必须是其它某个已经存在的 Rational 对象的引用,这个对象包含了两个对象相乘的结果。

        一个函数只能有两种方法创建一个新对象:在堆栈里或在堆上在堆栈里创建对象时伴随着一个局部变量的定义,采用这种方法,就要这样写 operator*:

//  写此函数的第一个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
这个方法应该被否决,因为我们的目标是避免构造函数被调用,但 result必须要象其它对象一样被构造。另外,这个函数还有另外一个更严重的问题, 它返回的是一个局部对象的引用,关于这个错误,条款 31 进行了深入的讨论。那么,在堆上创建一个对象然后返回它的引用呢?基于堆的对象是通过使用 new 产生的,所以应该这样写 operator*。

  

//  写此函数的第二个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
Rational *result =
new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}
       首先,你还是 得负担构造函数调用的开销,因为 new 分配的内存是通过调用一个适当的构造函数来初始化的(见条款 5 和 M8) 。另外,还有一个问题: 谁将负责用 delete 来删除掉 new 生成的对象呢?(不要返回函数内部用new初始化的指针的引用

也许,你会想你比一般的熊——或一般的程序员——要聪明;也许,你注意到在堆栈和堆上创建对象的方法避免不了对构造函数的调用;也许,你想起了我们最初的目标是为了避免这种对构造函数的调用;也许,你有个办法可以只用一个构造函数来搞掂一切;也许,你的眼前出现了这样一段代码: operator*返回一个“在函数内部定义的静态 Rational 对象”的引用


//  写此函数的第三个错误方法
inline const Rational& operator*(const Rational& lhs,
const Rational& rhs)
{
static Rational result; //  将要作为引用返回的
//   静态对象
lhs 和 rhs  相乘,结果放进 result ;
return result;
}

bool operator==(const Rational& lhs, const Rational& rhs); 
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*刚被调用每个调用返回operator*内部的静态 Rational 对象的引用。于是,上面的语句实际上是 请求operator==对“operator*内部的静态 Rational 对象的值”和“operator*内部的静态 Rational 对象的值”进行比较,这样的比较不相等才怪呢!

总结:为什么不能返回对象的引用。

首先:对象可以在堆,堆栈,全局数据区开辟。

1、当对象在堆栈中开辟,局部对象开辟在堆栈中,当函数返回时,局部对象被析构掉,返回局部对象的引用是致命的错误
2、当对象用new建立,即开辟在堆中,那么返回堆中这个对象的引用,逻辑上是没问题的,但是,返回该对象的引用后,该对象的析构交给谁来负责。

3、当对象建立在全局数据区,用static修饰,如上所述,对于一些情况会产生逻辑错误。


以上讨论可以归结为:当需要在返回引用和返回对象间做决定时,你的职责是选择可以完成正确功能的那个。至于怎么让这个选择所产生的代价尽可能的小,那是编译器的生产商去想的事。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值