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

from:https://blog.csdn.net/iicy266/article/details/11906457

知识背景

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

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

正题

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

示例代码讲解

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

[cpp] view plaincopy
  1. class Base 
  2. {  
  3.    public:  
  4.     ~Base() {  
  5.       cout << "~Base()" << endl;  
  6.      }  
  7. };  

[cpp] view plaincopy
  1. class Derived1 : public Base {  
  2.  public:  
  3.   Derived1():name_(new string("NULL")) {}  //通过new创建在堆上的对象
  4.   Derived1(const string& n):name_(new string(n)) {}  
  5.   
  6.   ~Derived1() {  
  7.     delete name_;  
  8.     cout << "~Derived1(): name_ has been deleted." << endl;  
  9.   }  
  10.   
  11.  private:  
  12.   string* name_;  
  13. };  
  14.   
  15. class Derived2 : public Base {  
  16.  public:  
  17.   Derived2():name_(new string("NULL")) {}  
  18.   Derived2(const string& n):name_(new string(n)) {}  
  19.   
  20.   ~Derived2() {  
  21.     delete name_;  
  22.     cout << "~Derived2(): name_ has been deleted." << endl;  
  23.   }  
  24.   
  25.  private:  
  26.   string* name_;  
  27. };  
我们看下面对其析构情况进行测试:

[cpp] view plaincopy
  1. int main() {  
  2.   Derived1* d1 = new Derived1(); //d1为Derived1类的指针,它指向一个在堆上创建的Derived1的对象,需要delete调用 
  3.   Derived2 d2 = Derived2("Bob");  //d2为一个在栈上创建的对象,执行结束后,自动调用析构
  4.   delete d1;  
  5.   return 0;  
  6. }  
d1为Derived1类的指针,它指向一个在堆上创建的Derived1的对象;d2为一个在栈上创建的对象。其中d1所指的对象需要我们显式的用delete调用其析构函数;d2对象在其生命周期结束时,系统会自动调用其析构函数。看下其运行结果:

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

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

[cpp] view plaincopy
  1. int main() {  
  2.   Base* base[2] = {  new Derived1(), new Derived2("Bob") };  
  3.   for (int i = 0; i != 2; ++i) {  
  4.     delete base[i];      
  5.   }  
  6.   return 0;  
  7. }  

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

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

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

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

[cpp] view plaincopy
  1. class Base {  
  2.  public:  
  3. virtual ~Base() {  
  4.   cout << "~Base()" << endl;  
  5. }  
  6. };  
再看下其运行结果:


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


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458354.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第二章 Python基本元素:数字、字符串和变量

Python有哪些内置的数据类型&#xff1a; True False #布尔型 42 100000000 #整型 3.14159 1.0e8 #浮点型 abcdes #字符串 2.1 变量、名字和对象 python中统一的形式是什么&#xff1f; 对象&#xff0c;所有的对象都是以对象的形式存在…

Mac - 设置NSButton 的背景色

- (void)drawRect:(NSRect)dirtyRect {[super drawRect:dirtyRect];[[NSColor clearColor] setFill];NSRectFill(self.bounds);self.wantsLayer YES;self.layer.cornerRadius 8;self.layer.masksToBounds YES; } 转载于:https://www.cnblogs.com/741162830qq/p/5157046.html…

C++中static关键字作用总结

from&#xff1a;https://www.cnblogs.com/songdanzju/p/7422380.html1.先来介绍它的第一条也是最重要的一条&#xff1a;隐藏。&#xff08;static函数&#xff0c;static变量均可&#xff09; 当同时编译多个文件时&#xff0c;所有未加static前缀的全局变量和函数都具有全局…

C Primer Plus 第7章 C控制语句:分支和跳转 7.4 一个统计字数的程序

2019独角兽企业重金招聘Python工程师标准>>> 首先&#xff0c;这个程序应该逐个读取字符&#xff0c;并且应该有些方法判断何时停止&#xff1b;第二&#xff0c;它应该能够识别并统计下列单位&#xff1a;字符、行和单词。下面是伪代码描述&#xff1a; read a cha…

深入理解extern用法

from&#xff1a;https://blog.csdn.net/z702143700/article/details/46805241一、 extern做变量声明 l 声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。 我们一般把所有的全局变量和全局函数的实现都放在一个*.cpp文件里面&#xff0c;然后用一个同名的*.h文…

收集整理的非常有用的PHP函数

为什么80%的码农都做不了架构师&#xff1f;>>> 1、PHP加密解密 2、PHP生成随机字符串 3、PHP获取文件扩展名&#xff08;后缀&#xff09; 4、PHP获取文件大小并格式化 5、PHP替换标签字符 6、PHP列出目录下的文件名 7、PHP获取当前页面URL 8、PHP强制下载文件 9、…

进程间的通信方式——pipe(管道)

from&#xff1a;https://blog.csdn.net/skyroben/article/details/715133851.进程间通信每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到&#xff0c;所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内…

bash中(),{},(()),[],[[]]的区别

前言:在bash中遇到各种括号&#xff0c;同时在进行字符数值比较判定时&#xff0c;总是不断出现问题&#xff0c;于是通过参考《advanced bash-scripting guide》&#xff0c;同时在centos 6.7版本上进行测试&#xff0c;现况总结如下。如有纰漏&#xff0c;望指正。一.()一个命…

多进程和多线程之间的通信方式及通信实现步骤小结

进程间通信方式 # 管道( pipe )&#xff1a;管道是一种半双工的通信方式&#xff0c;数据只能单向流动&#xff0c;而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 # 有名管道 (namedpipe) &#xff1a; 有名管道也是半双工的通信方式&#xff0c;…

highcharts 显示网格

2019独角兽企业重金招聘Python工程师标准>>> xAxis: { gridLineColor: #197F07, gridLineWidth: 1 }, yAxis: { gridLineColor: #197F07, gridLineWidth: 2 }, 转载于:https://my.oschina.net/LingBlog/blog/697885

Cheat—— 给Linux初学者和管理员一个终极命令行备忘单

编译自&#xff1a;http://www.tecmint.com/cheat-command-line-cheat-sheet-for-linux-users/作者&#xff1a; Avishek Kumar原创&#xff1a;LCTT https://linux.cn/article-3760-1.html译者&#xff1a; su-kaiyao原文稍有改动 当你不确定你所运行的命令&#xff0c;尤其是…

串口操作之API篇 CreateFile

CreateFile http://bbs.fishc.com/thread-72944-1-1.html(出处: 鱼C论坛) ------------------------------------------------------------------------CreateFile用于打开串口,如果操作成功,返回一个句柄.1 function CreateFile(lpFileName: PChar; dwDesiredAccess, dwShareM…

云数据库·ApsaraDB 产品6月刊

【重点关注】RDS发布新规格 RDS于5月下旬发布新产品规格&#xff0c;新规格对齐ECS配置:1.连接数大幅提升 互联网型的应用特点是发展快速&#xff0c;在云上应用层会基于VM进行横向扩展&#xff0c;对数据库的要求除了资…

【同行说技术】教你玩转iOS的5篇技术干货

在文章《iOS从小白到大神必读资料汇总一到四》这个系列中&#xff0c;深入介绍了iOS入门学习及进阶的相关技术资料&#xff0c;今天小编继续发布iOS学习的5篇干货文章&#xff0c;赶紧来看看吧 &#xff01;喜欢写博客的工程师博主可以加工程师博主交流群&#xff1a;391519124…

Qt Console Application 与 Qt GUI Application互转

在桌面开发中&#xff0c;总的来说&#xff0c;包含两种类型的应用程序&#xff1a;无界面的Console程序和有界面的GUI程序。Qt也不例外&#xff0c;包含Qt Console Application和Qt GUI Application。一、Qt Console Application在VS2015中创建一个Qt Console Application&…

Create Volume 操作(Part I) - 每天5分钟玩转 OpenStack(50)

2019独角兽企业重金招聘Python工程师标准>>> 前面已经学习了 Cinder 的架构和相关组件&#xff0c;从本节我们开始详细分析 Cinder 的各种操作&#xff0c;首先讨论 Cinder 如何创建 volume。 Create 操作流程如下&#xff1a; 客户&#xff08;可以是 OpenStack 最…

如何有效解决C与C++的相互调用问题

from&#xff1a;https://blog.csdn.net/gobitan/article/details/1532769在实际工作中可能经常要进行C和C的混合编程&#xff0c;C调用C语言的代码通常都比较容易&#xff0c;但也有一些细节需要注意。C要调用C的代码就略为麻烦一些&#xff0c;因为C不支持面向对象的特征。一…

Eclipse开发工具之崩溃和备份

1.通过在命令行中输入“where java”&#xff0c;找到除jdk目录下的所有java相关程序&#xff0c;直接删掉&#xff08;一般会在C:WINDOWSsystem32下&#xff09;以后再也不用怕找不到目录了 2.内存不足&#xff0c;打开Eclipse目录下的eclipse.ini&#xff0c;把里面的-Xmx512…

IOS-网络(监听网络状态)

1 //2 // BWNetWorkTool.h3 // IOS_0131_检测网络状态4 //5 // Created by ma c on 16/1/31.6 // Copyright © 2016年 博文科技. All rights reserved.7 //8 9 #import <Foundation/Foundation.h> 10 11 interface BWNetWorkTool : NSObject 12 ///是否是WiFi …

C++中的friend详细解析

C中的友元机制允许类的非公有成员被一个类或者函数访问&#xff0c;友元按类型分为三种&#xff1a;普通非类成员函数作为友元,类的成员函数作为友元&#xff0c;类作为友元。友元包括友元的声明以及友元的定义。 友元的声明默认为了extern&#xff0c;就是说友元类或者友元函数…