zoukankan      html  css  js  c++  java
  • [Effective C++ --007]为多态基类声明virtual析构函数

    引言:

        我们都知道类的一个很明显的特性是多态,比如我们声明一个水果的基类:

    class Fruit {
    public:
        Fruit() {};
        ~Fruit(){};
    }

       那么我们根据这个Fruit基类可以派生出以下的子类:

       class Apple:public Fruit{};
       class Orange:public Fruit{};

    那么问题来了,如果我们想经由一个基类指针去删除一个派生类Apple,且我们有以下的方法

    Fruit * foo(){
           Apple * p = new Apple();
           return p; // 子转父,大丈夫
    }

    接下来似乎顺理成章,我们只需要完成以下的main函数即可了。

    int main() {
            A *p = foo();
            delete p;
            return 0;
    }

     但是实际运行就会发现:Apple对象并未被删除!

    这是为什么呢?因为foo()返回的指针指向一个Apple对象,其中的Apple的成员变量很可能未被销毁,而且Apple的析构函数也未被执行起来。于是造成了一个局部销毁的局面。

    一、解决方案

    其实很简单,只需要将基类中的析构函数定义为虚函数即可。

    class Fruit {
    public:
        Fruit() {};
        virtual ~Fruit(){};   ←
    }

    这样在删除p的时候,会先调用Apple的析构函数,然后再调用Fruit的析构函数,最后删除整个Apple对象。

    二、扩展

      在书中所说:

       在string等STL容器使用的时候,即时class不带virtual函数,可能还是会被是否为虚函数的问题折腾。比如我们拿string来作为一个基类声明以下的class

    #include <iostream>
    #include<string>
    using namespace std;
    
    class D : public string
    {
    public:
        D(string i) {};
        ~D() {};
        string i;
    };
    
    int main()
    {
        D* d = new D("DD");
        string *p;
        p = d;
        delete p;     // 书中描写此处会发生内存泄露,但是十分不解,上面的p =d 是子赋给父,按理是不应该出现问题的
    
        return 0;
    }

    调试结果在VS环境下也不会出现错误,不清楚是为什么,还有待调查。

    2014.11.11 调查结果:

    在上述结果中,确实不会出现问题,因为只是单纯的删除了p,但是d中间的值有没有得到删除就不能得到确定了。

    这跟我们删除d的目的是相悖的。

    添加以下的代码来做说明:

    class D : public string
    {
    public:
        D(string str, int i): s(str), length(i){};
        ~D() {
            cout << "call me";
        };
    private:
        string s;
        int length;
    };
    
    int main()
    {
        D* d = new D("DD", 5);
        string *p;
        p = d;
        delete p;  // 此时释放p,并不会调用~D(),因此原本给length赋值了5并不会被清除!!!
    
        return 0;
    }    

     三、纯虚析构函数的调用顺序

    在基类存在纯虚析构函数的时候,析构函数的运作方式是:派生class的析构函数先被调用,然后再调用基类的析构函数。

     1 #include <iostream>
     2 #include<string>
     3 
     4 using namespace std;
     5 
     6 class A {
     7 public:
     8     A() {};
     9     virtual ~A() = 0;
    10 };
    11 A::~A() {
    12     cout << "~A" << endl;
    13 }
    14 class B : public A {
    15 public:
    16     B() {};
    17     ~B() {
    18         cout << "~B" << endl;
    19     }
    20 };
    21 
    22 int main()
    23 {
    24     B* b = new B();
    25     delete b;
    26 
    27     return 0;
    28 }

    运行后输出: 

    ~B
    ~A

    ■总结:

    1.带多态性质的基类应该声明一个virtual析构函数,如果class带有任何的虚函数,那么它就应该拥有一个virtual析构函数。

    2.class的设计目的如果不是作为基类使用,或不是为了具备多态性,就不应该声明virtual析构函数。

  • 相关阅读:
    什么是系统打开文件表?
    为什么Unix只允许对非目录文件实行勾链?
    在Unix系统中,主存索引节点和辅存索引节点从内容上比较有什么不同,为什么要设置主存索引节点?
    Unix系统的文件目录项的内容是什么,这样处理的好处是什么?
    Unix系统使用的地址索引结构有什么特点?
    Unix文件系统的主要特点是什么?
    什么是打开文件操作,什么关闭文件操作,引入这两个操作的目的是什么?
    在非洲运营互联网系统-基础建设
    这一年在非洲(4年一轮回总结完结)
    4年一轮回(后半部)
  • 原文地址:https://www.cnblogs.com/hustcser/p/4087787.html
Copyright © 2011-2022 走看看