[翻译] Effective C++, 3rd Edition, Item 21: 当你必须返回一个 object(对象)时不要试图返回一个 reference(引用)(上)

来源:互联网 发布:数控铣床编程八卦图形 编辑:程序博客网 时间:2024/05/01 21:55

Item 21: 当你必须返回一个 object(对象)时不要试图返回一个 reference(引用)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

一旦程序员抓住 objects pass-by-value(传值)的效率隐忧(参见 Item 20),很多人就会成为狂热的圣战分子,誓要根除 pass-by-value(传值)的罪恶,无论它隐藏多深。他们不屈不挠地追求 pass-by-reference(传引用)的纯度,但他们全都犯了一个致命的错误:他们开始传递引向并不存在的 objects 的引用。这可不是什么好事。

考虑一个代表 rational numbers(有理数数字)的 class,包含一个将两个 rationals(有理数)相乘的函数:

class Rational {
public:
  Rational(int numerator = 0,               // see Item 24 for why this
           int denominator = 1);            // ctor isn't declared explicit

  ...

private:
  int n, d;                                 // numerator and denominator

friend
   const Rational                           // see Item 3 for why the
     operator*(const Rational& lhs,         // return type is const
               const Rational& rhs);
};

operator* 的这个版本以 by value(传值)方式返回它的结果,而且如果你没有担心那个 object 的构造和析构的成本,你就是在推卸你的专业职责。如果你不是迫不得已,你并不想为这样的一个 object 付出成本。所以问题就在这里:你是迫不得已吗?

哦,如果你能用返回一个 reference(引用)来作为代替,你就不是迫不得已。但是,请记住一个 reference(引用)仅仅是一个 name(名字),一个 existing(实际存在)的 object 的 name(名字)。无论何时只要你看到一个 reference(引用)的 declaration(声明),你应该立刻问自己它是什么东西的另一个名字,因为它必定是 something(某物)的另一个名字。在这个 operator* 的情况下,如果函数返回一个 reference(引用),它必须返回某个已存在的而且其中包含两个 objects 相乘的产物的 Rational object 的 reference(引用)。

当然没有什么理由期望这样一个 object(对象)在调用 operator* 之前就存在。也就是说,如果你有

Rational a(1, 2);                        // a = 1/2
Rational b(3, 5);                        // b = 3/5

Rational c = a * b;                      // c should be 3/10

似乎没有理由期望那里碰巧已经存在一个值为十分之三的有理数。不是这样的,如果 operator* 返回这样一个数的 reference(引用),它必须自己创建那个数字 object。

一个函数创建一个新 object 仅有两种方法:在 stack(栈)上或者在 heap(堆)上。stack(栈)上的生成物通过定义一个 local variable(局部变量)而生成。使用这个策略,你可以用这种方法试写 operator*

const Rational& operator*(const Rational& lhs,   // warning! bad code!
                          const Rational& rhs)
{
  Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
  return result;
}

你可以立即否决这种方法,因为你的目标是避免一个 constructor(构造函数)调用,而 result 正像任何其它 object 一样必须被构造。一个更严重的问题是这个函数返回一个引向 result 的 reference(引用),但是 result 是一个 local object(局部对象),而 local objects(局部对象)在函数退出时被销毁。那么,这个 operator* 的版本不会返回引向一个 Rational 的 reference(引用)——它返回引向一个 ex-Rational(前 Rational);一个 former Rational(曾经的 Rational);一个空洞的、恶臭的、腐败的,从前是一个 Rational 但永不会再是的尸体的 reference(引用),因为它已经被销毁了。任何调用者甚至于没有来得及匆匆看一眼这个函数的返回值就立刻进入了未定义行为的领地。事实就是,任何返回一个 reference to a local object(引向局部对象的引用)的函数都是错误的。(对于任何返回一个 pointer to a local object(指向局部对象的指针)的函数同样成立。)

那么,让我们考虑一下在 heap(堆)上构造一个 object 并返回引向它的 reference(引用)的可能性。heap-based objects(基于堆的对象)通过使用 new 而开始存在,所以你可以像这样写一个 heap-based(基于堆)的 operator*

const Rational& operator*(const Rational& lhs,   // warning! more bad
                          const Rational& rhs)   // code!
{
  Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
  return *result;
}

哦,你还是必须要付出一个 constructor(构造函数)调用的成本,因为通过 new 分配的内存要通过调用一个适当的 constructor(构造函数)进行初始化,但是现在你有另一个问题:谁适合 delete 你用 new 做出来的 object?

即使调用者尽职尽责且一心向善,为了在下面这样合理地使用的情境中避免泄漏,它们也做不了什么:

Rational w, x, y, z;

w = x * y * z;                     // same as operator*(operator*(x, y), z)

这里,在同一个语句中有两个 operator* 的调用,因此 new 被使用了两次,这两次都需要使用 delete 来取消。但是 operator* 的客户没有合理的办法进行那些调用,因为他们没有合理的办法取得隐藏在通过调用 operator* 返回的 references(引用)后面的 pointers(指针)。这是一个早已注定的 resource leak(资源泄漏)。

(此篇未完,点击此处,接下篇)

原创粉丝点击