《C++ Primer》之面向对象编程(四)

  • 纯虚函数

在前面所提到过的 Disc_item 类提出了一个有趣的问题:该类从 Item_base 继承了 net_price 函数但没有重定义该函数。因为对 Disc_item 类而言没有可以给予该函数的意义,所以没有重定义该函数。在我们的应用程序中,Disc_item 不对应任何折扣策略,这个类的存在只是为了让其他类继承。//也就是说,Disc_item继承了Item_base中的虚函数net_price,但是由于Disc_item不需要net_price,所以它没有重定义net_price

我们不想让用户定义 Disc_item 对象,相反,Disc_item 对象只应该作为 Disc_item 派生类型的对象的一部分而存在。但是,正如已定义的,没有办法防止用户定义一个普通的 Disc_item 对象。这带来一个问题:如果用户创建一个 Disc_item 对象并调用该对象的 net_price 函数,会发生什么呢?从前面章节的讨论中了解到,结果将是调用从 Item_base 继承而来的 net_price 函数,该函数产生的是不打折的价格。很难说用户可能期望调用 Disc_itemnet_price 会有什么样的行为。真正的问题在于,我们宁愿用户根本不能创建这样的对象。可以使 net_price 成为纯虚函数,强制实现这一设计意图并正确指出 Disc_itemnet_price 版本没有意义的。在函数形参表后面写上 = 0 以指定纯虚函数

class Disc_item : public Item_base {
     public:
         double net_price(std::size_t) const = 0;
     };

将函数定义为纯虚能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。重要的是,用户将不能创建 Disc_item 类型的对象

试图创建抽象基类的对象将发生编译时错误:

// Disc_item declares pure virtual functions
     Disc_item discounted; // error: can't define a Disc_item object
     Bulk_item bulk;       // ok: Disc_item subobject within Bulk_item

含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象

  • 容器与继承

我们希望使用容器(或内置数组)保存因继承而相关联的对象。但是,对象不是多态的,这一事实对将容器用于继承层次中的类型有影响。例如,书店应用程序中可能有购物篮,购物篮代表顾客正在购买的书。我们希望能够在 multiset中存储储购买物,要定义 multiset,必须指定容器将保存的对象的类型。将对象放进容器时,复制元素。

如果定义 multiset 保存基类类型的对象:

multiset<Item_base> basket;
     Item_base base;
     Bulk_item bulk;
     basket.insert(base);  // ok: add copy of base to basket
     basket.insert(bulk);  // ok: but bulk sliced down to its base part

加入派生类型的对象时,只将对象的基类部分保存在容器中。记住,将派生类对象复制到基类对象时,派生类对象将被切掉

容器中的元素是 Item_base 对象,无论元素是否作为 Bulk_item 对象的副本而建立,当计算元素的 net_price 时,元素将按不打折定价。一旦对象放入了 multiset,它就不再是派生类对象了。因为派生类对象在赋值给基类对象时会被“切掉”,所以容器与通过继承相关的类型不能很好地融合。

不能通过定义容器保存派生类对象来解决这个问题。在这种情况下,不能将 Item_base 对象放入容器——没有从基类类型到派生类型的标准转换。可以显式地将基类对象强制转换为派生类对象并将结果对象加入容器,但是,如果这样做,当试图使用这样的元素时,会产生大问题:在这种情况下,元素可以当作派生类对象对待,但派生类部分的成员将是未初始化的。

唯一可行的选择可能是使用容器保存对象的指针。这个策略可行,但代价是需要用户面对管理对象和指针的问题,用户必须保证只要容器存在,被指向的对象就存在。如果对象是动态分配的,用户必须保证在容器消失时适当地释放对象。

  • 句柄类与继承

C++ 中面向对象编程的一个颇具讽刺意味的地方是,不能使用对象支持面向对象编程,相反,必须使用指针或引用。例如,下面的代码段中: 

void get_prices(Item_base object,
                     const Item_base *pointer,
                     const Item_base &reference)
     {
         // which version of net_price is called is determined at run time
         cout << pointer->net_price(1) << endl;
         cout << reference.net_price(1) << endl;

         // always invokes Item_base::net_price
         cout << object.net_price(1) << endl;
     }

通过 pointerreference 进行的调用在运行时根据它们所绑定对象的动态类型而确定。但是,使用指针或引用会加重类用户的负担。//什么负担?参考:http://blog.chinaunix.net/uid-26275986-id-3848567.html

http://blog.sina.com.cn/s/blog_3f864d610101h45z.html

//引用下面一段话解释:“如何实现一个类似“购物车”的数据结构呢?用过淘宝的同学们一定都晓得“购物车”应用,可以记录不同的商品,并且相同的商品可以显示次数,最后计算出总额。如果用C++来实现的话,当然是首选容器对象了。由于是统计可以重复的对象,所以可以使用multiset关联容器。现在你有两个选择:一是将商品对象放入容器中成为容器对象;二是容器中存储指向商品对象的指针或引用。第一种方法明显是不合适的,因为商品之间可能存在着继承关系,那么我们的容器类型时基类还是派生类呢?派生类型的容器元素的派生部分就会是未定义状态,而基类型的容器元素则会失去派生类部分成员。如果使用指针或者引用呢?可以避免上面的问题,但是用户必须负责管理指针或引用,尤其是防止“悬垂指针”的出现,即要确保指针在,对象在;对象失,指针无。这无疑会加重用户的开发负担。那么有没有更好的解决办法呢?有的,就是我们今天要讨论的句柄类。”

 C++ 中一个通用的技术是定义包装(cover)类(句柄类)。句柄类存储和管理基类指针。指针所指对象的类型可以变化,它既可以指向基类类型对象又可以指向派生类型对象。包装了继承层次的句柄有两个重要的设计考虑因素:

 像对任何保存指针的类一样,必须确定对复制控制做些什么。包装了继承层次的句柄通常表现得像一个智能指针或者像一个值;

句柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基本层次中的对象。

对于这些选项没有正确的选择,决定取决于继承层次的细节,以及类设计者希望程序员如何与那些类相互作用。

前 面我们接触过“计数类”用来管理指针,这里的句柄类类似,但是比简单的计数类增加了一些其他的功能,因为我们除了利用它管理指针,还希望便捷的使用其指向 的对象。这里的主要idea就是将指针的管理工作封装到一个类中,类中起码具有两个数据成员,一个指向对象的指针和一个计数指针,计数归零时意味着要释放 对象和句柄类。

  • 指针型句柄

定义句柄类:

像第一个例子一样,我们将定义一个名为 Sales_item 的指针型句柄类,表示 Item_base 层次Sales_item用户将像使用指针一样使用它:用户将 Sales_item 绑定到 Item_base 类型的对象并使用 *-> 操作符执行 Item_base 的操作:

// bind a handle to a Bulk_item object
     Sales_item item(Bulk_item("0-201-82470-1", 35, 3, .20));

     item->net_price();   // virtual call to net_price function

但是,用户不必管理句柄指向的对象,Sales_item 类将完成这部分工作。当用户通过 Sales_item 类对象调用函数时,将获得多态行为//用户不用管是基类还是派生类调用的net_price(),都由Sales_item自动完成

 迄今为止,我们已经使用过的使用计数式类,都使用一个伙伴类来存储指针和相关的使用计数。这个例子将使用不同的设计,如下图所示。Sales_item 类将有两个数据成员,都是指针:一个指针将指向 Item_base 对象,而另一个将指向使用计数。Item_base 指针可以指向 Item_base 对象也可以指向 Item_base 派生类型的对象。通过指向使用计数,多个 Sales_item 对象可以共享同一计数器

除了管理使用计数之外,Sales_item 类还将定义解引用操作符和箭头操作符:

// use counted handle class for the Item_base hierarchy
     class Sales_item {
     public:
         // default constructor: unbound handle
         Sales_item(): p(0), use(new std::size_t(1)) { }
         // attaches a handle to a copy of the Item_base object
         Sales_item(const Item_base&);
         // copy control members to manage the use count and pointers
         Sales_item(const Sales_item &i):
                           p(i.p), use(i.use) { ++*use; }
         ~Sales_item() { decr_use(); }
         Sales_item& operator=(const Sales_item&);
         // member access operators
         const Item_base *operator->() const { if (p) return p;
             else throw std::logic_error("unbound Sales_item"); }
         const Item_base &operator*() const { if (p) return *p;
             else throw std::logic_error("unbound Sales_item"); }
     private:
         Item_base *p;        // pointer to shared item
         std::size_t *use;    // pointer to shared use count
         // called by both destructor and assignment operator to free pointers
         void decr_use()
              { if (--*use == 0) { delete p; delete use; } }
     };

使用计数式复制控制:

复制控制成员适当地操纵使用计数和 Item_base 指针。复制 Sales_item 对象包括复制两个指针和将使用计数加 1.析构函数将使用计数减 1,如果计数减至 0 就撤销指针。因为赋值操作符需要完成同样的工作,所以在一个名为 decr_use 的私有实用函数中实现析构函数的行为。赋值操作符比复制构造函数复杂一点:

// use-counted assignment operator; use is a pointer to a shared use count
     Sales_item&
     Sales_item::operator=(const Sales_item &rhs)
     {
         ++*rhs.use;
         decr_use();//原有的类型被覆盖掉,所以会自减
         p = rhs.p;
         use = rhs.use;
         return *this;
     }

赋值操作符像复制构造函数一样,将右操作数的使用计数加 1 并复制指针;它也像析构函数一样,首先必须将左操作数的使用计数减 1,如果使用计数减至 0 就删除指针。像通常对赋值操作符一样,必须防止自身赋值。这个操作符通过首先将右操作数的使用计数减 1 来处理自身赋值。如果左右操作数相同,则调用 decr_use 时使用计数将至少为 2。该函数将左操作数的使用计数减 1 并进行检查,如果使用计数减至 0,则 decr_use 将释放该对象中的 Item_base 对象和 use 对象。剩下的是从右操作数向左操作数复制指针,像平常一样,我们的赋值操作符返回左操作数的引用。

除了复制控制成员以外,Sales_item 定义的其他函数是是操作函数 operator*operator->,用户将通过这些操作符访问 Item_base 成员。因为这两个操作符分别返回指针和引用,所以通过这些操作符调用的函数将进行动态绑定

我们只定义了这些操作符的 const 版本,因为基础 Item_base 层次中的成员都是 const 成员。

构造句柄:

我们句柄有两个构造函数:默认构造函数创建未绑定的 Sales_item 对象,第二个构造函数接受一个对象,将句柄与其关联。第一个构造函数容易定义:将 Item_base 指针置 0 以指出该句柄没有关联任何对象上。构造函数分配一个新的计数器并将它初始化为 1。

第二个构造函数难一点,我们希望句柄的用户创建自己的对象,在这些对象上关联句柄。构造函数将分配适当类型的新对象并将形参复制到新分配的对象中,这样,Sales_item 类将拥有对象并能够保证在关联到该对象的最后一个 Sales_item 对象消失之前不会删除对象//这里就是将句柄类关联到他要“保护”的类

  • 复制未知类型

要实现接受 Item_base 对象的构造函数,必须首先解决一个问题:我们不知道给予构造函数的对象的实际类型。我们知道它是一个 Item_base 对象或者是一个 Item_base 派生类型的对象。句柄类经常需要在不知道对象的确切类型时分配对象的新副本。Sales_item 构造函数是个好例子。解决这个问题的通用方法是定义虚操作进行复制,我们称将该操作命名为 clone

为了句柄类,需要从基类开始,在继承层次的每个类型中增加 clone,基类必须将该函数定义为虚函数://利用clone实现在使用句柄类时对类的动态绑定

class Item_base {
     public:
         virtual Item_base* clone() const
                            { return new Item_base(*this); }
     };

每个类必须重定义该虚函数。因为函数的存在是为了生成类对象的新副本,所以定义返回类型为类本身:

class Bulk_item : public Item_base {
     public:
         Bulk_item* clone() const
             { return new Bulk_item(*this); }
     };

对于派生类的返回类型必须与基类实例的返回类型完全匹配的要求,但有一个例外。这个例外支持像这个类这样的情况。如果虚函数的基类实例返回类类型的引用或指针,则该虚函数的派生类实例可以返回基类实例返回的类型的派生类(或者是类类型的指针或引用)。

定义句柄构造函数:

Sales_item::Sales_item(const Item_base &item):
                 p(item.clone()), use(new std::size_t(1)) { }

像默认构造函数一样,这个构造函数分配并初始化使用计数,它调用形参的 clone 产生那个对象的(虚)副本。如果实参是 Item_base 对象,则运行 Item_baseclone 函数;如果实参是 Bulk_item 对象,则执行 Bulk_itemclone 函数。

  • 句柄的使用

使用 Sales_item 对象可以更容易地编写书店应用程序。代码将不必管理 Item_base 对象的指针,但仍然可以获得通过 Sales_item 对象进行的调用的虚行为。

比较Sales_item对象:

在编写函数计算销售总数之前,需要定义比较 Sales_item 对象的方法。要用 Sales_item 作为关联容器的关键字,必须能够比较它们。但为 Sales_item 句柄类定义 operator> 可能是个坏主意:当使用 Sales_item 作关键字时,只想考虑 ISBN,但确定相等时又想要考虑所有数据成员。//所以,所使用的比较可能是不同的比较函数

让我们比较容易的部分开始,定义一个函数用于比较 Sales_item 对象:

// compare defines item ordering for the multiset in Basket
     inline bool
     compare(const Sales_item &lhs, const Sales_item &rhs)
     {
         return lhs->book() < rhs->book();
     }

使用带关联容器的比较器:

如果考虑一下如何使用比较函数,就会认识到,它必须作为容器的部分而存储。任何在容器中增加或查找元素的操作都要使用比较函数。原则上,每个这样的操作可以接受一个可选的附加实参,表示比较函数。但是,这种策略容易导致出错:如果两个操作使用不同的比较函数,顺序可能会不一致。不可能预测实际上会发生什么。要有效地工作,关联容器需要对每个操作使用同一比较函数。然而,期望用户每次记住比较函数是不合理的,尤其是,没有办法检查每个调用使用同一比较函数。因此,容器记住比较函数是有意义的。通过将比较器存储在容器对象中,可以保证比较元素的每个操作将一致地进行。基于同样的理由,容器需要知道元素类型,为了存储比较器,它需要知道比较器类型。原则上,通过假定比较器是一个函数指针,该函数接受两个容器的 key_type 类型的对象并返回 bool 值,容器可以推断出这个类型。不幸的是,这个推断出的类型可能限制太大。首先,应该允许比较器是函数对象或是普通函数。即使我们愿意要求比较器为函数,这个推断出的类型也可能仍然太受限制了,毕竟,比较函数可以返回 int 或者其他任意可用在条件中的类型。同样,形参类型也不需要与 key_type 完全匹配,应该允许可以转换为 key_type 的任意形参类型。所以,要使用 Sales_item 的比较函数,在定义 multiset 时必须指定比较器类型。在我们的例子中,比较器类型是接受两个 const Sales_item 引用并返回 bool 值的函数。

首先定义一个类型别名,作为该类型的同义词:

// type of the comparison function used to order the multiset
     typedef bool (*Comp)(const Sales_item&, const Sales_item&);

接着需要定义 multiset,保存 Sales_item 类型的对象并在它的比较函数中使用这个 Comp 类型。关联容器的每个构造函数使我们能够提供比较函数的名字。可以这样定义使用 compare 函数的空 multiset

 std::multiset<Sales_item, Comp> items(compare);

这个定义是说,items 是一个 multiset,它保存 Sales_item 对象并使用 Comp 类型的对象比较它们。multiset 是空的——我们没有提供任何元素,但我们的确提供了一个名为 compare 的比较函数。当在 items 中增加或查找元素时,将用 compare 函数对 multiset 进行排序。

写一个势力作为参考:

class Item_base {
public:
    Item_base(const std::string &book = "",
        double sales_price = 0.0) :
        isbn(book), price(sales_price) { }
    std::string book() const { return isbn; }
    // returns total sales price for a specified number of items
    // derived classes will override and apply different discount algorithms
    virtual double net_price(std::size_t n) const
    {
        cout << "net_price of Item_base" << endl;
        return n * price;
    }
    virtual ~Item_base() { }

    virtual Item_base* clone() const
    {
        return new Item_base(*this);
    }
    
private:
    std::string isbn;     // identifier for the item
protected:
    double price;         // normal, undiscounted price
};

class Bulk_item : public Item_base {
public:
    // redefines base version so as to implement bulk purchase discount policy
    Bulk_item(string s, double p, int m, double d) :Item_base(s, p), min_qty(m), discount(d){}
    double net_price(std::size_t) const;
    Bulk_item* clone() const
    {
        return new Bulk_item(*this);
    }

private:
    std::size_t min_qty; // minimum purchase for discount to apply
    double discount;     // fractional discount to apply
};

double Bulk_item::net_price(std::size_t n) const
{
    cout << "net_price of Bulk_item:"<< endl;
    return discount * min_qty * n;
}


// use counted handle class for the Item_base hierarchy
class Sales_item {
public:
    // default constructor: unbound handle
    Sales_item() : p(0), use(new std::size_t(1)) { }
    // attaches a handle to a copy of the Item_base object
    Sales_item(const Item_base&);
    // copy control members to manage the use count and pointers
    Sales_item(const Sales_item &i) :
        p(i.p), use(i.use) {//item3和item1指向同一个对象
        cout << "Assignment" << endl;
        ++*use;
    }
    ~Sales_item() { 
        cout << "Sales_item destructor" << endl;
        decr_use();
    }
    Sales_item& operator=(const Sales_item&);
    // member access operators
    const Item_base *operator->() const {//重载操作符之后,当你想操作Item_base及其派生类对象时,可以直接用Sale_item对象进行操作
        if (p) return p;//因为这里返回了基类的指针,而基类可以实现动态绑定
        else throw std::logic_error("unbound Sales_item");
    }
    const Item_base &operator*() const {
        if (p) return *p;
        else throw std::logic_error("unbound Sales_item");
    }
    void show_use(){ cout << *use << endl; }
private:
    Item_base *p;        // pointer to shared item 关键 实现动态绑定
    std::size_t *use;    // pointer to shared use count 关键 实现计数
    // called by both destructor and assignment operator to free pointers
    void decr_use()
    {
        if (--*use == 0) { delete p; delete use; }
    }
};


// use-counted assignment operator; use is a pointer to a shared use count
Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
    cout << "Copy" << endl;
    ++*rhs.use;
    decr_use();
    p = rhs.p;
    use = rhs.use;
    return *this;
}

Sales_item::Sales_item(const Item_base&rhs){
    cout << "constructor of Sales_item" << endl;
    p = rhs.clone();//不能直接用p指向rhs本身,因为rhs是const
    //rhs是引用,通过引用调用clone可以实现动态绑定
    use = new std::size_t(1);
}

运行:

输出:

再考虑:

会崩溃:

如果依靠句柄类:

输出:

可以避免使用悬空指针。

通过本节,我们提炼出两个重点知识点即智能指针句柄类,后边再进行深入研究。

  • 回顾智能指针

智能指针的介绍可以参考:http://blog.csdn.net/hackbuteer1/article/details/7561235

 当我们使用类的指针进行操作的时候,很可能出现对悬垂指针的操作。智能指针的作用就是先设计一个类,然后用这个类去接管对类指针的操作,凡是对原先类的指针的操作,都通过这个智能指针去操作,以避免出现对悬垂指针的操作。因此,智能指针就是在保证指针安全的情况下模拟类指针行为的类。句柄类和智能指针的区别就在于,句柄类把你对基类、派生类的操作都承接过来。句柄类通过在其数据成员中加入一个基类指针,来实现对基类和派生类的动态调用,你无需再考虑自己操作的是基类还是派生类对象。

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

实现引用计数,通常使用具体的类来封装计数和类指针:

class U_Ptr
{
    friend class HasPtr;
    int *ip;
    size_t use;
    U_Ptr(int *p) : ip(p), use(1)
    {
        cout << "U_ptr constructor called !" << endl;
    }
    ~U_Ptr()
    {
        delete ip;
        cout << "U_ptr distructor called !" << endl;
    }
};

HasPtr类需要一个析构函数来删除指针。但是,析构函数不能无条件的删除指针。条件就是引用计数。如果该对象被两个指针所指,那么删除其中一个指针,并不会调用该指针的析构函数,因为此时还有另外一个指针指向该对象。看来,智能指针主要是预防不当的析构行为,防止出现悬垂指针。

 

这个类U_Ptr封装了对象p,以及对象p被引用的次数。HasPtr操作p直接操作U_Ptr即可。用户操作p直接操作HasPtr即可。U_Ptr仅仅是封装了指针p,而由于不再是直接操作p,而是变成间接的操作p,由于操作的不再是p而是U_Ptr对象,所以各种操作方式也得随之改变,HasPtr的诞生就是封装了对U_Ptr的各种操作。//总之,改变了操作对象(由p变为U_Ptr),就要新封装一套对其的操作

 

如上图所示,HasPtr就是智能指 针,U_Ptr为计数器;里面有个变量use和指针ip,use记录了*ip对象被多少个HasPtr对象所指。假设现在又两个HasPtr对象p1、 p2指向了U_Ptr,那么现在我delete  p1,use变量将自减1,  U_Ptr不会析构,那么U_Ptr指向的对象也不会析构,那么p2仍然指向了原来的对象,而不会变成一个悬空指针。当delete p2的时候,use变量将自减1,为0。此时,U_Ptr对象进行析构,那么U_Ptr指向的对象也进行析构,保证不会出现内存泄露。

(1)不管指针成员。复制时只复制指针,不复制指针指向的对象。当其中一个指针把其指向的对象的空间释放后,其它指针都成了悬浮指针。这是一种极端
    (2)当复制的时候,即复制指针,也复制指针指向的对象。这样可能造成空间的浪费。因为指针指向的对象的复制不一定是必要的。
   (3) 第三种就是一种折中的方式。利用一个辅助类来管理指针的复制。原来的类中有一个指针指向辅助类,辅助类的数据成员是一个计数器和一个指针(指向原来的)(此为本次智能指针实现方式)。

 HasPtr 智能指针的声明如下,保存一个指向U_Ptr对象的指针,U_Ptr对象指向实际的int基础对象,代码如下:

class HasPtr
{
public:
    // 构造函数:p是指向已经动态创建的int对象指针  
    HasPtr(int *p, int i) : ptr(new U_Ptr(p)), val(i)
    {
        cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
    }

    // 复制构造函数:复制成员并将使用计数加1  
    HasPtr(const HasPtr& orig) : ptr(orig.ptr), val(orig.val)
    {
        ++ptr->use;
        cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl;
    }

    // 赋值操作符  
    HasPtr& operator=(const HasPtr&);

    // 析构函数:如果计数为0,则删除U_Ptr对象  
    ~HasPtr()
    {
        cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;
//         if (--ptr->use == 0)
//             delete ptr;//释放数据成员
    }

    // 获取数据成员  
    int *get_ptr() const
    {
        return ptr->ip;
    }
    int get_int() const
    {
        return val;
    }

    // 修改数据成员  
    void set_ptr(int *p) const
    {
        ptr->ip = p;
    }
    void set_int(int i)
    {
        val = i;
    }

    // 返回或修改基础int对象  
    int get_ptr_val() const
    {
        return *ptr->ip;
    }
    void set_ptr_val(int i)
    {
        *ptr->ip = i;
    }
private:
    U_Ptr *ptr;   //指向使用计数类U_Ptr ,专门用来计数的一个类 
    int val;
    //U_Ptr up;
};

假设现在又两个智能指针p1、 p2,一个指向内容为42的内存,一个指向内容为100的内存,如下图:

 

现在,我要做赋值操作,p2 = p1。对赋值操作符定义如下:

 

HasPtr& HasPtr::operator=(const HasPtr& rhs){
    cout << "Assignment operator" << endl;
    if (&rhs == this)
    {
        cout << "Self assignment" << endl;
        return *this;
    }
    ++rhs.ptr->use;
    if (--ptr->use == 0)
    {
        delete ptr;//这个HasPtr只是引用计数为0了,不代表它后边不会再被用到
        //它后边还有可能被其它的HasPtr赋值而重新被启用,所以没有调用析构函数,而只是释放ptr
    }
    ptr = rhs.ptr;   // 复制U_Ptr指针  
    val = rhs.val;   // 复制int成员  
    return *this;
}

 

做完赋值操作后,那么就成为如下图所示了。红色标注的就是变化的部分:


而还要注意的是,重载赋值操作符的时候,一定要注意的是,检查自我赋值的情况:
if (&rhs == this)
    {
        cout << "Self assignment" << endl;
        return *this;
    }

运行
int main(void)
{
    int *pi = new int(42);
    HasPtr *hpa = new HasPtr(pi, 100);    // 构造函数  //只能指针接管了类指针pi
    HasPtr *hpb = new HasPtr(*hpa);     // 复制构造函数  
    HasPtr *hpc = new HasPtr(*hpb);     // 复制构造函数  
    HasPtr hpd = *hpa;     // 复制构造函数  
    int *pi2 = new int(43);
    //调用了赋值操作符
    HasPtr *hpe = new HasPtr(pi2, 100);
    *hpe = *hpa;
    //Self assignment
    *hpe = *hpe;

    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
    hpc->set_ptr_val(10000);
    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
    hpd.set_ptr_val(10);
    cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
    delete hpa;
    delete hpb;
    delete hpc;
    cout << hpd.get_ptr_val() << endl;
    system("pause");
    return 0;
}

输出结果:

 

 

 

这里注意一点,析构函数需要delete指针:

原因是,析构函数中不会自动删除指针,所以必须手动删除,以免内存泄露

但是如果数据成员是类的对象,比如说:

那么对于在调用析构函数时也会自动调用up的析构函数:

 

 

posted @ 2016-03-13 22:26  _No.47  阅读(229)  评论(0编辑  收藏  举报