为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数?

本博客可能随时删除或隐藏,请关注微信公众号,获取永久内容。

微信搜索“编程笔记本”,获取更多信息
------------- codingbook2020 -------------

今天我们来谈一谈面试 C++ 工程师时经常被谈到的一个问题:为什么析构函数必须是虚函数?为什么默认的析构函数不是虚函数?

首先,我们看一下百度百科对虚函数是怎么定义的:

在某基类中声明为 virtual并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名 ( 参数表 ) { 函数体 };实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。

好了,现在我们大概知道什么是虚函数,虚函数就是类中使用关键 virtual修饰的成员函数,其目的是为了实现多态性。

那么什么是多态性呢?

所谓多态性,顾名思义就是“多个性态”。更具体一点的就是,用一个名字定义多个函数,这些函数执行不同但相似的工作。最简单的多态性的实现方式就是函数重载模板,这两种属于静态多态性。还有一种是动态多态性,其实现方式就是我们今天要说的虚函数

回归正题。

一、为什么析构函数必须是虚函数?

当然了,这么说其实是不太严谨的,因为我完全可以将析构函数定义成非虚函数。这个我们后面再说。

首先我们需要知道析构函数的作用是什么。析构函数是进行类的清理工作,具体来说就是释放构造函数开辟的内存空间和资源,当然我们完全可以在析构函数中进行任何我们想要的操作,比如下面我们给出的示例代码,就在析构函数中打印提示信息。

前面我们在介绍虚函数的时候就说到,为实现多态性,可以通过基类的指针或引用访问派生类的成员。也就是说,声明一个基类指针,这个基类指针可以指向派生类对象

下面我们来看一个例子:

#include <iostream>

using namespace std;

class Father {
public:
    ~Father() {
        cout << "class Father destroyed" << endl;
    }
};

class Son : public Father {
public:
    ~Son() {
        cout << "class Son destroyed" << endl;
    }
};

int main() {
    Father* p = new Son;
    delete p;

    return 0;
}

/*
运行结果:
class Father destroyed
*/

上面的示例程序中,我们定义了两个类,一个基类,一个派生类,派生类公有继承父类。为了描述简单,这两个类只定义了析构函数,并在析构函数中输出提示信息。在主函数中,我们声明了一个基类的指针,并用一个派生类的实例去初始化这个基类指针,随后删除这个指针。我们看到程序运行的结果,只有基类的析构函数被调用。

为什么会这样呢?指针明明指向的是派生类对象,那删除这个指针,为何只有基类的析构函数被调用,而派生类的析构函数却没有调用呢?

我们先把问题留在这里,接下来我们看看,若析构函数被定义成虚函数会怎么样呢?

#include <iostream>

using namespace std;

class Father {
public:
    virtual ~Father() {
        cout << "class Father destroyed" << endl;
    }
};

class Son : public Father {
public:
    ~Son() {
        cout << "class Son destroyed" << endl;
    }
};

int main() {
    Father* p = new Son;
    delete p;

    return 0;
}

/*
运行结果:
class Son destroyed
class Father destroyed
*/

当基类的析构函数被定义成虚函数时,我们再来删除这个指针时,先调用派生类的析构函数,再调用基类的析构函数,很明显这才是我们想要的结果。因为指针指向的是一个派生类实例,我们销毁这个实例时,肯定是希望即清理派生类自己的资源,同时又清理从基类继承过来的资源。而当基类的析构函数为非虚函数时,删除一个基类指针指向的派生类实例时,只清理了派生类从基类继承过来的资源,而派生类自己独有的资源却没有被清理,这显然不是我们希望的。

所以说,如果一个类会被其他类继承,那么我们有必要将被继承的类(基类)的析构函数定义成虚函数。这样,释放基类指针指向的派生类实例时,清理工作才能全面进行,才不会发生内存泄漏。

二、为什么默认的析构函数不是虚函数?

那么既然基类的析构函数如此有必要被定义成虚函数,为何类的默认析构函数却是非虚函数呢?

首先一点,语言设计者如此设计,肯定是有道理的。

原来是因为,虚函数不同于普通成员函数,当类中有虚成员函数时,类会自动进行一些额外工作。这些额外的工作包括生成虚函数表虚表指针,虚表指针指向虚函数表。每个类都有自己的虚函数表,虚函数表的作用就是保存本类中虚函数的地址,我们可以把虚函数表形象地看成一个数组,这个数组的每个元素存放的就是各个虚函数的地址。
这样一来,就会占用额外的内存,当们定义的类不被其他类继承时,这种内存开销无疑是浪费的。

这样一说,问题就不言而喻了。当我们创建一个类时,系统默认我们不会将该类作为基类,所以就将默认的析构函数定义成非虚函数,这样就不会占用额外的内存空间。同时,系统也相信程序开发者在定义一个基类时,会显示地将基类的析构函数定义成虚函数,此时该类才会维护虚函数表和虚表指针。

这个问题至此就解释完成了,祝大家面试顺利!

本博客可能随时删除或隐藏,请关注微信公众号,获取永久内容。

识别下方二维码关注我,或微信搜索**“编程笔记本”**,获取更多信息。

  • 82
    点赞
  • 214
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值