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

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

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

如:类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. 2015苏州大学ACM-ICPC集训队选拔赛(1) 1006

    取金币 Time Limit : 3000/1000ms (Java/Other)   Memory Limit : 65535/32768K (Java/Other) Total Submissio ...

  2. UESTC 1437

    LCA模板题 随便找的倍增模板... #include<bits/stdc++.h> using namespace std; const int maxn = 1e5+11; int t ...

  3. c#生产/消费RabbitMQ

    public sealed class JsonSerializer { public static byte[] Serialize(object message) { return Encodin ...

  4. rest_framework 的验证,权限,频率

    回到顶部 快速实例 Quickstart 回到顶部 序列化 创建一个序列化类 简单使用 开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之 ...

  5. bootstrap多选框

    不多说,先上图片 本多选框是用的bootstrap的样式为基础,将弹出框css改造,然后自己写的js得到. 下面为全部页面的代码,需要的可以自己改动js,得到自己需要的效果 <!DOCTYPE ...

  6. java拦截器的使用

    转载: https://www.cnblogs.com/liangblog/p/7234757.html https://blog.csdn.net/reggergdsg/article/detail ...

  7. epoll中坑人的地方再次学习

    https://blog.csdn.net/linuxheik/article/details/73294658

  8. maya2012安装失败如何卸载重装

    AUTODESK系列软件着实令人头疼,安装失败之后不能完全卸载!!!(比如maya,cad,3dsmax等).有时手动删除注册表重装之后还是会出现各种问题,每个版本的C++Runtime和.NET f ...

  9. Object.create 以及 Object.setPrototypeOf

    第一部分 Object.crate() 方法是es5中的关于原型的方法, 这个方法会使用指定的原型对象以及属性去创建一个新的对象. 语法 Object.create(proto, [ properti ...

  10. StreamWrite类

    FileStream类,该对象只能以字节形式读取/写入数据,这就使得操作非常困难. 一般有了FileStream对象,都会借用StreamWrite对象或StreamReader对象的方法来处理文件. ...