首页 > 代码库 > 《Effective C++》学习笔记——条款21

《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********************************************

《Effective C++》学习笔记——条款21