什么样的情况下才需要虚析构函数?

类需要控制自己的对象执行一系列操作时发生什么样的行为,这些操作包括:创建(对象)、拷贝、移动、赋值和销毁。在继承体系中,如果一个类(基类或其派生的类)没有定义拷贝控制操作,则编译器将自动的为其合成一个。即为合成的拷贝控制。

在基类的拷贝控制中,由于继承关系导致的最大影响就是:基类通常应该定义一个‘虚析构函数’。用以动态的分配继承体系中的对象。

如:类A,B,C,D有如下继承关系(代码1):

1
2
3
4
class A;
class B:public A;
class C:public B;
class D:public C;

其中:类A定义如下(代码2):

1
2
3
4
5
class A {
public:
    //其他函数
    virtual ~A()=default;//用于动态绑定的析构函数

};

当我们delete一个A* item 类型的指针时,该指针可能是指向A的,也可能指向的是B,C,D中的一个,编译器在delete时必须弄清楚到底应该执行A,B,C,D中哪一个类的析构函数。此时需要编译器进行动态绑定(即只有运行时才能知道到底item 指向的是那个类)。当在基类A中定义的析构函数为虚析构函数时,无论A的派生类(B,C,D)使用的是合成的析构函数还是自己定义的析构函数,它们都是虚析构函数。说人话就是:你老祖姓虚,传到你还是姓虚,你儿子孙子都得姓虚(千万别较真女生~~~),不管这儿孙是你血缘的还是你自己领养的,都得虚!

举个例子(代码3):

1
2
3
4
A *item = new A;  //此时item指向的就是A,静态类型于动态类型一致(这就是你本人)
delete item;  //调用A自己的析构函数(自杀了,杀的是你自己)
item = new B;  //静态类型为A,动态类型为B(此时你的血脉传到了你儿子身上,item是你儿子了!)
delete item;   //调用B自己的析构函数(你儿子要自杀,此时死的是你儿子,和你无关)

如果基类A的析构函数不是虚的(虚函数),则delete时,如果item指向的不是A,而是B或其他A的派生类,则会产生未定义的行为,未定义的行为通常会导致BUG。

那么问题来了:什么样的情况下才需要虚析构函数呢?是所有类都应该有吗?

通过基类的指针来删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

简单解释一下,派生类B中所有的属性以操作(Bp)不仅有B自己定义的属性、操作(Bself),还有继承自A的属性、操作(Aself),即Bp=Bself+Aself;

如代码3,当delete一个指向B的item时(其实item的类类型为A),如果A中的析构函数不是虚的,则只会删除Aself部分,因为item的类类型其实是A,只是指向了其派生类对象。但是在A的析构函数里其实并没有Bself部分,那这部分就删不掉了--这就是所谓的内存泄漏!只有A的析构函数是虚的,才能删除的不仅有Aself,还有Bself,即Bp全部被删除了。这才是正确的。

同时,并不是所有类都需要将析构函数定义成虚的。因为编译器在编译时会给类添加一个虚函数表,里面来存放虚函数指针,如果都定义成虚的,这样就会增加类的存储空间。浪费了!不用作基类,也不需要为虚的!不需要通过基类的指针来操作派生类的对象时,基类的析构函数不应该是虚的。

这里借用一下文章代码:什么时候要用虚析构函数?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ClxBase
{
public:
     ClxBase() {};
     virtual ~ClxBase() {};
     virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase
{
public:
     ClxDerived() {};
     ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
     void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
     代码
ClxBase *pTest = new ClxDerived;
pTest->DoSomething();
delete pTest;

正常情况应该输出:

1
2
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!

如果将类ClxBase的析构函数定义为非虚(去掉前面的那个virtual),则输出为:

1
Do something in class ClxDerived!

根本没有调用ClxDerived的析构函数哦~~~

同样,在什么时候要用虚析构函数?中,提出了一个这样的问题:

为什么继承一个没有虚析构函数的类是危险的?

这个问题吗其实上面已经解释过了,会导致删不完!内存泄漏问题。当你公有继承创建一个从基类继承的相关类时,指向新类对象中的指针和引用实际上都指向了起源的对象。因为析构函数不是虚函数,所以当你delete一个这样的类时,C++就不会调用析构函数链。

C++学习之虚析构函数的更多相关文章

  1. C++学习24 虚析构函数

    在C++中,构造函数用于在创建对象时进行初始化工作,不能声明为虚函数.因为在执行构造函数前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪一个 ...

  2. 【【C++ Primer 第15章】 虚析构函数

    学习资料 • C++中基类的析构函数为什么要用virtual虚析构函数 虚析构函数 1. 正文 直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏.具体地说,如果派生类中申请了内存空 ...

  3. 虚析构函数? vptr? 指针偏移?多态数组? delete 基类指针 内存泄漏?崩溃?

    五条基本规则: 1.如果基类已经插入了vptr, 则派生类将继承和重用该vptr.vptr(一般在对象内存模型的顶部)必须随着对象类型的变化而不断地改变它的指向,以保证其值和当前对象的实际类型是一致的 ...

  4. C++:探究纯虚析构函数以及实现数组的高速排序与链表的归并排序

    C++:探究纯虚析构函数以及实现数组的高速排序与链表的归并排序 标签: 数据结构 数组 链表 高速排序 归并排序 抽象类 虚继承 by 小威威 1.介绍 本篇博文将通过课后作业的(15 C++ Hom ...

  5. c++虚析构函数

    虚析构函数的作用主要是当通过基类指针删除派生类对象时,调用派生类的析构函数(如果没有将不会调用派生类析构函数) #include <iostream> using namespace st ...

  6. EC笔记,第二部分:7.为多态基类声明虚析构函数

    7.为多态基类声明虚析构函数 1.为多态基类声明虚析构函数 code1: class A{ public: int* a; A():a(new int(5)) {} ~A(){ delete a; } ...

  7. C++浅析——继承类内存分布和虚析构函数

    继承类研究 1. Code 1.1 Cbase, CTEST为基类,CTest2为其继承类,并重新申明了基类中的同名变量 class CBase { public: int Data; CBase() ...

  8. 虚析构函数(√)、纯虚析构函数(√)、虚构造函数(X)

    from:http://blog.csdn.net/fisher_jiang/article/details/2477577 一. 虚析构函数 我们知道,为了能够正确的调用对象的析构函数,一般要求具有 ...

  9. 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数

    一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...

随机推荐

  1. A. The number of positions

    A. The number of positions time limit per test 0.5 second memory limit per test 256 megabytes input ...

  2. Windows下 virtualenv 使用

    Windows下 virtualenv 使用 win python virtualenv 首先在电脑上安装两个不同版本的python mkvirtualenv --python C:\Python34 ...

  3. myEclipse更换SVN登录账号

    Win10下 找到下面路径删除其下的所有文件 C:\Users\Administrator\AppData\Roaming\Subversion\auth\svn.simple AppData默认隐藏 ...

  4. mysql中添加中文存储和显示功能

    1. 在 /etc/mysql/my.cnf中添加 [mysqld]character-set-server=utf8 [client]default-character-set=utf8 2. 检查 ...

  5. IDEA部署Express工程

    1.下载并安装Nodejs 2.通过Nodejs的NPM工具安装全局安装express工具,命令如下: npm install -g express@XXX npm install -g expres ...

  6. Apache和PHP的相关配置

      一.Apache2.4的配置和安装a. 打开Apache24/conf/httpd.conf文件进行编辑.1).38行改为Define SRVROOT "D:/myphp2017/Apa ...

  7. 小程序 页面到详情的id传递

    比如电影列表页跳转到电影详情页 在movie.js获取movieId; processDoubanData: function (moviesDouban, settedKey, categoryTi ...

  8. C#本期本周的算法

    C#根据当前时间获取,本周,本月,本季度等时间段 DateTime dt = DateTime.Now; //当前时间 DateTime startWeek = dt.AddDays( - Conve ...

  9. 关于jqeury中attr()和prop()方法

    在平时工作中老会被一样的问题困住,浪费时间,这里做一个简要笔记. 在使用jquery给元素设置属性的时候,如果是一般的div,p等正常元素设置属性的话,使用attr()足以,但是在给拥有checked ...

  10. 跨平台图表控件TeeChart使用教程:将图表数据导出为XML格式

    在开发者使用TeeChart进行开发的过程中,不管是在设计时或者运行时都可以使用的图表导出对话框将图表数据轻易地导出为XML格式: TeeChart最新版那下载地址 上图为TeeChart导出对话框的 ...