条款21(一):必须返回对象时,别妄想返回其reference

版权声明:仅供参考与学习交流 https://blog.csdn.net/lym940928/article/details/81915397

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

Don’t try to return a reference when you must return an object.
本章分为两个部分。

在上一章中我们了解到,pass-by-value有很多效率方面的问题,因此pass-by-reference可能是一种比较好的方法。
但是!盲目的用reference可能会造成这样的错误:

  • 开始传递一些references指向其实并不存在的对象!

举个例子,对于一个用以表现有理数(rational number)的class,内含一个函数用来计算一个有理数的乘积:

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1);
    //分子numerator和分母denominator
    ...
private:
    int n, d;
    friend
      const Rational
        operator* (const Rational& lhs, 
                   const Rational& rhs);
};

在上面的代码中,这个版本的operator* 是以by value的方法返回其计算结果——一个rational对象。对于这样的返回方法,代价如何?
假如说我们进行修改,使用reference进行传递,就不需要付出代价了。但是:

  • 所谓的reference,只是一个名称,代表着某个既有对象。即,它一定是某物的另一个名称。

就像上面的operator* ,如果他返回一个reference,那么后者一定指向某个既有的Rational对象,内含两个Rational对象的乘积。
因此,我们不能期望这样一个内含乘积的Rational对象在调用operator* 之前就存在。也就是说:

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

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

对象的创建

一般来说,函数创建新对象有两种方法:

  • stack空间创建
  • heap空间创建

Stack

如果我们定义一个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对象在函数退出之前就被销毁了。因此,此时operator* 所指向的Rational,是一个已经被销毁的Rational!于是,此时将会陷入“无定义行为”的困境。
简单总结一句话:

  • 任何函数如果返回一个reference指向某个local对象,都会产生必然的错误!

Heap

因此,我们考虑在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.n * 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背后隐藏的那个指针。

  • 这势必会造成内存泄漏!

猜你喜欢

转载自blog.csdn.net/lym940928/article/details/81915397