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析构函数。

  • 相关阅读:
    python全栈-Day 4
    5个步骤实现软件质量的快速提升
    如何选择正确的静态应用安全测试(SAST)解决方案?
    安全工具箱必备技术之静态分析安全测试(SAST)
    精彩回顾:2020年自动化软件测试质量峰会
    怎样才能明智地利用代码覆盖率来最大限度地提高测试效率?
    软件开发你不可不知的那些事:如何有效减轻风险和质量债务?
    敏捷开发中不为人知的小秘密,你是否深有同感?
    当AI遇上API测试 — 敏捷开发已迎来革新时代!
    面对行业分析家和敏捷专家都认可的API测试,我们为什么会望而却步?
  • 原文地址:https://www.cnblogs.com/hustcser/p/4087787.html
Copyright © 2011-2022 走看看