c/c++ 基类析构函数为什么必须定义为虚函数?

c/c++ 基类析构函数为什么必须定义为虚函数?

1 现象

在c++ 实现多态时,我们经常会用到用基类来操作派生类,这样多便于上层接口的统一,而把基类定义为虚函数,就是为了释放基类句柄时只析构基类而不析构派生类的情况发生。
下面列举三种使用情况

  • 基类析构函数不是虚函数,不使用多态,创建派生类对象。
  • 基类析构函数不是虚函数,使用多态,创建派生类对象。
  • 基类析构函数是虚函数,使用多态,创建派生类对象。

以上几种情况对应代码如下:

#include <iostream>
#include <stdio.h>
using namespace std;
class BaseClass
{
public:
    BaseClass()
    {

    }
    ~BaseClass()
    {
        printf("fucn %s line %d\n",__FUNCTION__,__LINE__);
    }
    void do_something(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};

class DeriveClass : public BaseClass
{
public:
    DeriveClass()
    {
    }
    ~DeriveClass()
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
    void do_something(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};


class BaseClass2 
{
public:
    BaseClass2()
    {

    }
    ~BaseClass2()
    {
        printf("fucn %s line %d\n",__FUNCTION__,__LINE__);
    }
    void do_something2(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};

class DeriveClass2 : public BaseClass2
{
public:
    DeriveClass2()
    {
    }
    ~DeriveClass2()
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
    void do_something2(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};

class BaseClass3 
{
public:
    BaseClass3()
    {

    }
    virtual ~BaseClass3()
    {
        printf("fucn %s line %d\n",__FUNCTION__,__LINE__);
    }
    void do_something3(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};

class DeriveClass3 : public BaseClass3
{
public:
    DeriveClass3()
    {
    }
    ~DeriveClass3()
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
    void do_something3(void)
    {
        printf("fucn %s line %d\n", __FUNCTION__, __LINE__);
    }
};


int main(void)
{
	// 第一种情况没有使用多态,直接创建一个派生类指针,然后 new一个派生类对象给它,在delete 派生类指针时会自动析构,先析构派生类,再析构基类,没有内存泄漏
    DeriveClass *p = new DeriveClass();
    p->do_something();
    delete p;
   // 第二种情况使用多态,直接创建一个基类指针,然后 new一个派生类对象给它,在delete 派生类指针时会自动析构,只是析构基类的对象,而派生类并没有析构,造成内存泄漏
    BaseClass2 *p2 = new DeriveClass2();
    p2->do_something2();
    delete p2;
   // 第三种情况使用多态,直接创建一个基类指针,然后 new一个派生类对象给它,在delete 派生类指针时会自动析构,先析构派生类,再析构基类,没有内存泄漏
    BaseClass3 *p3 = new DeriveClass3();
    p3->do_something3();
    delete p3;
    
    return 0;
}

运行结果如下:

fucn do_something line 33
fucn ~DeriveClass line 29
fucn ~BaseClass line 13
fucn do_something2 line 51
fucn ~BaseClass2 line 47
fucn do_something3 line 84
fucn ~DeriveClass3 line 96
fucn ~BaseClass3 line 80

2 原因剖析

想要回收一个对象申请的资源,那么就需要调用析构函数,虽然我们没有显示地调用析构函数,但是编译器会帮我们默认的执行析构函数,比如说在上例中我们通过 new 的方式创建一个对象,new的时候回自动调用构造函数(这也就是malloc和new的主要区别之一,有机会详细讨论),而在 delete时编译器会自动执行析构函数,帮我们释放掉相关的资源。
通过上面的实验我们发现:如果基类的析构函数不是虚函数,在我们使用多态的情况下,我们在delete 基类对象并不会释放派生类的资源,只会释放基类的资源,原因如下:
基类指针指向了派生类的对象,而基类中的析构函数是非 virtual 的,虚函数是动态绑定的基础,动态绑定是多态的基础,动态绑定是发生在程序运行时。如果基类的析构函数不是virtual的,就不会发生动态绑定绑定,指针的静态类型为基类指针,因此delete时只会调用基类的析构函数,而不会调用派生类的析构函数,这样派生类申请的资源(动态申请的资源)得不到释放,从而造成内存泄漏,因此为了防止上述情况的发生我们统一将基类的析构函数写成 virtual 虚析构函数

3 扩展

构造函数为什么不能是虚函数?
动态绑定和静态绑定的问题?
c++虚函数实现原理?
关于以上问题,有机会进行讨论

©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:上身试试 返回首页