《Effective C++》学习笔记——条款21
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
四、Designs and Declarations
Rule 21:Don‘t try to return a reference when you must return an object
规则 21:必须返回对象时,别妄想返回其reference
1.原因
看了我们 条款20 ,如果领悟了 pass-by-value 的效率牵连层面,或许就恨不得都用 pass-by-reference 来替代。但这时,可能会出现一些致命的错误——开始传递一些指向其实并不存在的的对象。
先来看一下这个例子,这是一个表现 有理数 的类。
<span style="font-family:Comic Sans MS;">class Rational { public: Rational( int numerator = 0 , int denominator = 1 ); ... private: int n,d; // n 为分子,d 为分母 friend const Rational operator* ( const Rational& lhs, const Rational& rhs); };</span>
这里的 operator* 用 by value 方式返回其计算结果,但会有额外的开销,当然你也可以完全不管,但这明显逃避了你的专业责任,因为很简单的,改而传递 reference 就不需要支付额外代价。
但是,要记住所谓的 reference 只是个名称,代表某个既有对象。所以如果看到一个reference声明式,你应该问自己它的另一个名称是什么。
以上述 operator* 为例,如果它返回一个 reference,后者一定指向某个既有的Rational 对象,内含两个 Rational对象的乘积。
但是,我们无法期望这样的一个 Rational对象在 调用 operator* 之前就存在。
所以,如果 operator* 要返回一个 reference 指向如此数值,它必须自己创建那个 Rational对象。
2.解决
>1< 通过函数创建新对象。
函数创建新对象的途径有两种:在stack空间 或 在 heap 空间创建。
① 在stack空间创建
就是定义一个 local 变量,用这方法来写 operator* :
<span style="font-family:Comic Sans MS;">const Rational& operator* ( const Rational& lhs, const Rational& rhs) { Rational result( lhs.n * rhs.n , lhs.d * rhs.d ); // 糟糕的代码 return result; }</span>
这种做法不好,因为你的目标是 避免调用构造函数,而 result 却必须像任何对象一样的由构造函数够早起来。
但更严重的是,这个函数返回的reference是 local对象,而local对象在函数退出前就会被销毁。也就是说,reference 指向的是一个已经被销毁的对象。
② 在 heap内构造一个对象
Heap-based对象由 new 创建,所以它的 operator* :
<span style="font-family:Comic Sans MS;">const Rational& operator* ( const Rational& lhs, const Rational& rhs) { Rational* result = new Rational( lhs.n * rhs.n,lhs.d * rhs.d ); return *result; }</span>
但是这个依旧需要付出一个 调用构造函数 的代价,
而且这又有一个新的问题:谁该对着被你 new 出来的对象 执行 delete。
尤其是,遇到下面这段代码:
<span style="font-family:Comic Sans MS;">Rational w,x,y,z; w = x * y *z; // 等价于 operator*(operator*(x,y),z)</span>
在这里同一个语句内,调用了两个 operator* ,也就是说 new 了两次,即要 delete两次。
但是这里却没有合理的方法让 operator* 使用者进行这些 delete 的调用,因为没有合理的方法来获取 operator* 返回的references背后隐藏的指针。
这些都将导致 资源泄露!
>2< 让 operator* 返回的reference 指向一个被定义于函数内部 static 对象
因为上述,无论 on-the-stack 或 on-the-heap做法,都因为对 operator* 返回的结果调用构造函数而困扰,所以可以尝试这种方法。
<span style="font-family:Comic Sans MS;">const Rational& operator* ( const Rational& lhs, const Rational& rhs ) { static Rational result; result = ...; return result; }</span>
但是,如果遇到下面这种代码:
<span style="font-family:Comic Sans MS;">bool operator==( const Rational& lhs,const Rational& rhs); // 针对Rational而写的 operator== Rational a,b,c,d; ... if( (a*b) == (c*d) ) { // 当乘积相等,做适当动作 } else { // 当乘积不等,做相应动作 }</span>
每次判断 (a*b)==(c*d) 都将会是 true!
Why? 因为 两次operator* 虽然都各自改变了 static Rantional 对象值。但由于它们返回的都是 reference ,因此调用端看到的永远是 static Rantional对象的 "现值"!
>扩展 如果static Rantional 对象不行,那....static array 怎么样?
首先,你必须选择 array 大小n。 如果n太小,那空间可能不够;如果n太大,会因此降低程序效率,因为array内的每一个对象都会在第一次被调用时构造完成。
如果上面这个无法说服你,那下面这个理由呢? 如何将你需要的值放进array内。如果那么做成本又是多少。即使以vector替换array也不会让情况好转。
>3< 正确答案该出来了
让那个函数返回一个新对象。
<span style="font-family:Comic Sans MS;font-size:12px;">inline const Rantional operator* ( const Rational& lhs,const Rational& rhs ) { return Rational(lhs.n*rhs.n,lhs.d*rhs.d); }</span>
当然这么做,需要承担 operator* 返回值的构造和析构的成本。
但是要知道,C++和所有编程语言一样,允许编译器实现者施行最优化,用以改善产出码的效率。因此某些情况下,这些成本可以被安全的消除。
所以,当你必须在"返回一个reference和返回一个object"之间抉择时,你的工作就是挑出行为正确的那一个。然后让编译器厂商做剩余的东西吧。
3.请记住
★绝不要返回 pointer 或 reference 指向一个 local stack 对象,或返回 reference 指向一个 heap-allocated 对象,或返回 pointer 或 reference 指向一个local static 对象而有可能同时需要多个这样的对象。条款4已经为 "在单线程环境中合理返回reference指向一个local static对象"提供了一份设计实例。
**************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。