zoukankan      html  css  js  c++  java
  • Effective C++ 学习笔记(20)

    千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用


      先看第一种情况:返回一个局部对象的引用。它的问题在于,局部对象 ----- 顾名思义 ---- 仅仅是局部的。也就是说,局部对象是在被定义时创建,在离开生命空间时被销毁的。所谓生命空间,是指它们所在的函数体。当函数返回时,程序的控制离开了这个空间,所以函数内部所有的局部对象被自动销毁。因此,如果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它之前被销毁了。

      当想提高程序的效率而使函数的结果通过引用而不是值返回时,这个问题就会出现。

      

    class Rational { // 一个有理数类
    public:
    Rational(
    int numerator = 0, int denominator = 1);
    ~Rational();
    ...
    private:
    int n, d; // 分子和分母
    // 注意operator* (不正确地)返回了一个引用
    friend const Rational& operator*(const Rational& lhs,
    const Rational& rhs);
    };
    // operator*不正确的实现
    inline const Rational& operator*(const Rational& lhs,
    const Rational& rhs)
    {
    Rational result(lhs.n
    * rhs.n, lhs.d * rhs.d);
    return result;
    }

      这里,局部对象result 在刚进入operator*函数体时就被创建。但是,所有的局部对象在离开它们所在的空间时都要被自动销毁。具体到这个例子来说,result 是在执行return 语句后离开它所在的空间的。所以,如果这样写:

      

    Rational two = 2;
    Rational four
    = two * two; // 同operator*(two, two)

      函数调用时将发生如下事件:

    1. 局部对象result 被创建。
    2. 初始化一个引用,使之成为result 的另一个名字;这个引用先放在另一边,留做operator*的返回值。
    3. 局部对象result 被销毁,它在堆栈所占的空间可被本程序其它部分或其他程序使用。
    4.  用步骤2 中的引用初始化对象four。
      一切都很正常,直到第 4 步才产生了错误,借用高科技界的话来说,产生了"一个巨大的错误"。因为,第2 步被初始化的引用在第3 步结束时指向的不再是一个有效的对象,所以对象four 的初始化结果完全是不可确定的。
      教训很明显:别返回一个局部对象的引用。"那好,"你可能会说,"问题不就在于要使用的对象离开它所在的空间太早吗?我能解决。不要使用局部对象,可以用new 来解决这个问题。"象下面这样:
      
    // operator*的另一个不正确的实现
    inline const Rational& operator*(const Rational& lhs,
    const Rational& rhs)
    {
    // create a new object on the heap
    Rational *result =
    new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
    // return it
    return *result;
    }

      这个方法的确避免了上面例子中的问题,但却引发了新的难题。大家都知道,为了在程序中避免内存泄漏,就必须确保对每个用new 产生的指针调用delete,但是,这里的问题是,对于这个函数中使用的new,谁来进行对应的delete 调用呢?

      第一,大家都知道,程序员这类人是很马虎的。这不是指你马虎或我马虎,而是指,没有哪个程序员不和某个有这类习性的人打交道。想让这样的程序员记住无论何时调用operator*后必须得到结果的指针然后调用delete,这样的几率有多大呢?也是说,他们必须这样使用operator*:

      

    const Rational& four = two * two; // 得到废弃的指针;
    // 将它存在一个引用中
    ...
    delete
    &four; // 得到指针并删除

      这样的几率将会小得不能再小。记住,只要有哪怕一个operator*的调用者忘了这条规则,就会造成内存泄漏。

      返回废弃的指针还有另外一个更严重的问题,即使是最尽责的程序员也难以避免。因为常常有这种情况,operator*的结果只是临时用于中间值,它的存在只是为了计算一个更大的表达式。例如:

      

    Rational one(1), two(2), three(3), four(4);
    Rational product;
    product
    = one * two * three * four;

      product 的计算表达式需要三个单独的operator*调用,以相应的函数形式重写这个表达式会看得更清楚:

    product = operator*(operator*(operator*(one, two), three), four);

      是的,每个 operator*调用所返回的对象都要被删除,但在这里无法调用delete,因为没有哪个返回对象被保存下来。

      解决这一难题的唯一方案是叫用户这样写代码:

      

    const Rational& temp1 = one * two;
    const Rational& temp2 = temp1 * three;
    const Rational& temp3 = temp2 * four;
    delete
    &temp1;
    delete
    &temp2;
    delete
    &temp3;

      果真如此的话,你所能期待的最好结果是人们将不再理睬你。更现实一点,你将会在指责声中度日,或者可能会被判处10 年苦力去写威化饼干机或烤面包机的微代码。

      所以要记住你的教训:写一个返回废弃指针的函数无异于坐等内存泄漏的来临。

  • 相关阅读:
    POJ3693 Maximum repetition substring —— 后缀数组 重复次数最多的连续重复子串
    SPOJ
    POJ2774 Long Long Message —— 后缀数组 两字符串的最长公共子串
    POJ3261 Milk Patterns —— 后缀数组 出现k次且可重叠的最长子串
    POJ1743 Musical Theme —— 后缀数组 重复出现且不重叠的最长子串
    SPOJ
    AC自动机小结
    HDU3247 Resource Archiver —— AC自动机 + BFS最短路 + 状压DP
    POJ1625 Censored! —— AC自动机 + DP + 大数
    Herding
  • 原文地址:https://www.cnblogs.com/DanielZheng/p/2129586.html
Copyright © 2011-2022 走看看