虚析构函数的理论前提是

执行完子类的析构函数,那么父类的虚构函数必然会被执行。

那么当用delete释放一个父类指针所实例化的子类对象时,如果没有定义虚析构函数,那么将只会调用父类的析构函数,而不会调用子类的虚构函数,导致内存的泄漏。

故: 继承时,要养成的一个好习惯就是,基类析构函数中,加上virtual。

知识背景

要弄明白这个问题,首先要了解下C++中的动态绑定。

关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定、虚函数、多态实现

正题

直接的讲,C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。

示例代码讲解

现有Base基类,其析构函数为非虚析构函数。Derived1和Derived2为Base的派生类,这两个派生类中均有以string* 指向存储其name的地址空间,name对象是通过new创建在堆上的对象,因此在析构时,需要显式调用delete删除指针归还内存,否则就会造成内存泄漏。

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

  

class Derived1 : public Base {
public:
Derived1():name_(new string("NULL")) {}
Derived1(const string& n):name_(new string(n)) {} ~Derived1() {
delete name_;
cout << "~Derived1(): name_ has been deleted." << endl;
} private:
string* name_;
}; class Derived2 : public Base {
public:
Derived2():name_(new string("NULL")) {}
Derived2(const string& n):name_(new string(n)) {} ~Derived2() {
delete name_;
cout << "~Derived2(): name_ has been deleted." << endl;
} private:
string* name_;
};

  我们看下面对其析构情况进行测试:

d1为Derived1类的指针,它指向一个在堆上创建的Derived1的对象;d2为一个在栈上创建的对象。其中d1所指的对象需要我们显式的用delete调用其析构函数;d2对象在其生命周期结束时,系统会自动调用其析构函数。看下其运行结果:

刚才我们说,Base基类的析构函数并不是虚析构函数,现在结果显示,派生类的析构函数被调用了,正常的释放了其申请的内存资源。这两者并不矛盾,因为无论是d1还是d2,两者都属于静态绑定,而且其静态类型恰好都是派生类,因此,在析构的时候,即使基类的析构函数为非虚析构函数,也会调用相应派生类的析构函数。

下面我们来看下,当发生动态绑定时,也就是当用基类指针指向派生类,这时候采用delete显式删除指针所指对象时,如果Base基类的析构函数没有virtual,会发生什么情况?

int main() {
Base* base[2] = {
new Derived1(),
new Derived2("Bob")
};
for (int i = 0; i != 2; ++i) {
delete base[i];
}
return 0;
}

  

从上面结果我们看到,尽管派生类中定义了析构函数来释放其申请的资源,但是并没有得到调用。原因是基类指针指向了派生类对象,而基类中的析构函数却是非virtual的,之前讲过,虚函数是动态绑定的基础。现在析构函数不是virtual的,因此不会发生动态绑定,而是静态绑定,指针的静态类型为基类指针,因此在delete时候只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏,这是相当危险的:如果系统中有大量的派生类对象被这样创建和销毁,就会有内存不断的泄漏,久而久之,系统就会因为缺少内存而崩溃。

也就是说,在基类的析构函数为非虚析构函数的时候,并不一定会造成内存泄漏;当派生类对象的析构函数中有内存需要收回,并且在编程过程中采用了基类指针指向派生类对象,如为了实现多态,并且通过基类指针将该对象销毁,这时,就会因为基类的析构函数为非虚析构函数而不触发动态绑定,从而没有调用派生类的析构函数而导致内存泄漏。

因此,为了防止这种情况下内存泄漏的发生,最好将基类的析构函数写成virtual虚析构函数。

下面把Base基类的析构函数改为虚析构函数:

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

 

 

这样就会实现动态绑定,派生类的析构函数就会得到调用,从而避免了内存泄漏。

本文转载于:https://www.cnblogs.com/liushui-sky/p/5824919.html

C++中基类虚析构函数的作用及其原理分析的更多相关文章

  1. C++中基类的析构函数为什么要用virtual虚析构函数

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  2. 【C++】C++中基类的析构函数为什么要用virtual虚析构函数?

    正面回答: 当基类的析构函数不是虚函数,并且基类指针指向一个派生类对象,然后通过基类指针来删除这个派生类对象时,如果基类的析构函数不是虚析构函数,那么派生类的析构函数就不会被调用,从而产生内存泄漏 # ...

  3. 虚函数的使用 以及虚函数与重载的关系, 空虚函数的作用,纯虚函数->抽象类,基类虚析构函数使释放对象更彻底

    为了访问公有派生类的特定成员,可以通过讲基类指针显示转换为派生类指针. 也可以将基类的非静态成员函数定义为虚函数(在函数前加上virtual) #include<iostream> usi ...

  4. 构造函数为什么不能为虚函数 &amp; 基类的析构函数为什么要为虚函数

    一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数相应一个指向vtable虚函数表的指针,这大家都知道,但是这个指向vtable的指针事实上是存储在对象的内存空间的.问题出来了,假设构造函数 ...

  5. 基类的析构函数写成virtual虚析构函数

    虚函数作用:动态绑定,实现多态效果. 场景问题: 派生类中有资源需要回收,而在编程中采用多态,由基类的指针指向派生类,则在释放的时候,如果基类的析构函数不是virtual,则派生类的析构函数得不到释放 ...

  6. C++-基类的析构函数为什么要加virtual虚析构函数(转)

    知识背景 要弄明白这个问题,首先要了解下C++中的动态绑定. 关于动态绑定的讲解,请参阅:  C++中的动态类型与动态绑定.虚函数.多态实现 正题 直接的讲,C++中基类采用virtual虚析构函数是 ...

  7. C++虚析构函数的作用

    注:本文内容来源于zhice163博文,感谢作者的整理. 1.为什么基类的析构函数是虚函数? 在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生. 下面转自网络:源地址  ...

  8. C++多态性中基类析构函数声明为虚函数

    在用基类指针指向派生类时, 在基类析构函数声明为virtual的时候,delete基类指针,会先调用派生类的析构函数,再调用基类的析构函数. 在基类析构函数没有声明为virtual的时候,delete ...

  9. 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数

    1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...

随机推荐

  1. java,swift,oc互相转换,html5 web开发跨平台

    java,swift,oc互相转换,html5 web开发跨平台 写一个java->swift的程序,这个程序是做跨平台系统的核心部分swift和oc到java也在考虑之列Swift->J ...

  2. thinkphp相关

    thinkphp相关1.thinkphp调试sql方法:echo M("table_name")->getLastSql(); 2. 条件查询设置多个条件参数的写法:(1). ...

  3. 20145206邹京儒Exp6 信息搜集与漏洞扫描

    20145206邹京儒Exp6 信息搜集与漏洞扫描 一.实践过程记录 openvas漏洞扫描 1.openvas-check-setup来查看下他的安装状态: 如下图所示:在步骤7中出现错误,显示需要 ...

  4. Android项目开发一

    Android项目开发一   进度计划 1.第一周 开源中国注册账号:http://my.oschina.net/u/2511208,并上传Android HelloWorld程序代码 搭建Andro ...

  5. 《将博客搬至51CTO》

    想把你的博客搬家到51CTO吗?想拥有一键式搬家的体验吗? 就算家大业大不好搬也没关系,我们帮你! 51CTO推出专业的搬家工具,用最短的时间.最快的速度,为你在这儿搭建一个温馨的家. 在这儿,你可以 ...

  6. vijos 1096 津津的储存计划

    题目描述 Description 津津的零花钱一直都是自己管理.每个月的月初妈妈给津津300元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同. 为了让津津学习如何储蓄,妈妈提出,津津可以 ...

  7. 判断两个vector是否相等

    转载:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=26354188&id=3198604 #include < ...

  8. Spring Aop的理解和简单实现

    1.AOP概念 所说的面向切面编程其实就是在处理一系列业务逻辑的时候这一系列动作看成一个动作集合.比如连接数据库来说: 加载驱动-----获取class--------获取连接对象-------访问数 ...

  9. Code First技术介绍

    地址:https://wenku.baidu.com/view/5620b862eefdc8d376ee3258.html 仅供参考

  10. Linux 安装 mysql 转 http://www.cnblogs.com/fnlingnzb-learner/p/5830622.html

    到mysql官网下载mysql编译好的二进制安装包,在下载页面Select Platform:选项选择linux-generic,然后把页面拉到底部,64位系统下载Linux - Generic (g ...