C++虚析构函数的介绍与解析

第3.6章 C++类工作实战笔记:多态、虚函数、纯虚函数及虚析构函数最全代码总结 摘要 本文介绍了C++面向对象编程中多态、虚函数、纯虚函数以及虚析构函数的核心概念与应用。通过几何图形类的示例展示了静态多态与动态多态的区别,重点说明了虚函数实现运行时多态的机制。文章详细解析了纯虚函数创建抽象类的方法,并强调了虚析构函数在防止内存泄漏中的重要性。最后通过代码示例演示了虚析构函数和纯虚析构函数的工作原理,这些技术对于构建灵活的类层次结构(如自动驾驶中的传感器数据处理系统)具有重要价值,能显著提升代码的可维护性和健壮性。 阅读详情

原文地址:http://blog.youkuaiyun.com/xunyn/article/details/7439276 


当使用基类指针释放派生类的对象时,需要将基类的析构函数声明为virtual,这是C++的一条规范,今天花了些时间探讨了下。

先构建两个类:father类 和son类,简单定义下:

class Father
{
public:
 Father(void);
public:
 virtual ~Father(void);
 void description();
private:
 int m_i_gen;
};

子类

class Son: public Father
{
public:
 Son(void);
public: 
  ~Son(void);

 void description();

private:
 int m_i_num;

};

问题一:虚析构函数与普通虚函数有什么区别

    首先,在C++中,每个类都只有一个析构函数,比如在子类Son中,便不能再添加父类father的虚函数virtual ~Father(void)的实现版本,这与普通虚函数是不同,普通虚函数是要在派生类中,重新给出父类中虚函数的实现,并且函数名相同。

问题二:虚析构函数的实现机制

   虚析构函数,也是通过vftable来实现的,实际上当父类的析构函数声明为虚函数时,子类的析构函数也变成了虚函数(虽然两者函数名不同),即告诉编译器,我和我的派生类都需要动态(运行时)完成析构函数执行。

     下面通过代码来进行验证

Father::Father(void)
{
 m_i_gen=1;
 CString str;
 str.Format("This is Father's constructor call");
 std::cout<<str<<"\n";

}

Father::~Father(void)
{
 std::cout<<"Father destroy"<<"\n";
}
void Father::description()
{
 std::cout<<"I am a father"<<"\n";
}
子类

Son::Son(void)
{
 m_i_num=1;
 CString str;
 str.Format("This is Son's constructor call");
 std::cout<<str<<"\n";
}

Son::~Son(void)
{
 std::cout<<"Son destroy"<<"\n";
}

void Son::description()
{
 std::cout<<"I am a son"<<"\n";
}
void  Son:: test()
{
 Father fath;
 fath.description();
}

主函数

int _tmain(int argc, _TCHAR* argv[]){

  Father fath;
 Son son;
 Father *f;
 Son *s;
 f = new Son();
 delete f;
 return 0;
}

依旧是执行之后追踪变量s

-  s 0x0040ad22 {m_i_num=-1259292477 } Son *
-  Father {m_i_gen=1213847799 } Father
-  __vfptr 0xc01bd8f7 *
  [0] CXX0030: 错误: 无法计算表达式的值 
  m_i_gen 1213847799 int
  m_i_num -1259292477 int
出现了小插曲,vfptr的值竟然没发追踪到,可能是因为对象是在堆中申请的内存空间,所以进入反汇编

确定在004057C4 这条语句,调用了4198885 (十进制)的函数,即类Son的析构函数~Son();

 

对于栈上的对象son,其变量追踪为

-  son {m_i_num=1 } Son
-  Father {m_i_gen=1 } Father
-  __vfptr 0x00434738 const Son::`vftable' *
  [0] 0x004011e5 Son::`vector deleting destructor'(unsigned int) *
  m_i_gen 1 int
  m_i_num 1 int
其中将vfptr中[0]内的0x004011e5换算成十进制,刚好就4198885,也就是说虽然没有直接追踪到对象s的vfptr[0]的值,但其实际调用的是地址为4198885的函数,这也就是栈上的对象son的vfptr[0]的值,即s对象是通过虚析构函数,在若不声明虚析构函数的情况下,会调用Father类的析构函数,编程了通过vfptr(0)来调用了Son类的析构函数。

这与直接son对象和father对象直接调用析构函数是完全不一样的:

004057E7  mov         dword ptr [ebp-3Ch],0 
004057EE  mov         byte ptr [ebp-4],0 
004057F2  lea         ecx,[ebp-20h] 
004057F5  call        Son::~Son (401217h) 
004057FA  mov         dword ptr [ebp-4],0FFFFFFFFh 
00405801  lea         ecx,[ebp-28h] 
00405804  call        Father::~Father (4012E4h) 
00405809  mov         eax,dword ptr [ebp-3Ch]

这两个析构函数的调用,在编译时,已确定传入地址。

 

总的来说,使用基类指针,在堆上释放派生类对象,就必须将基类指针声明为虚函数,后续的工作C++会帮忙完成。C++使用“按照绝对位置查表”的方式实现多态,确实保证好较好的效率,但是相较于按“函数名称查表”的实现机制,更不够能体现面向对象模型语言的特点。



C++:构造函数和析构函数能否为虚函数?

简单回答是:构造函数不能为虚函数,而析构函数可以且常常是虚函数。

(1) 构造函数不能为虚函数

让我们来看看大牛C++之父 Bjarne Stroustrup 在《The C++ Programming Language》里是怎么说的:

To construct an object, a constructor needs the exact type of the object it is to create. Consequently,a constructor cannot be virtual. Furthermore, a constructor is not quite an ordinary function, In particular, it interacts with memory management in ways ordinary member functions don't. Consequently, you cannot have a ponter to a constructor.

--- From 《The C++ Progamming Language》15.6.2

然而大牛就是大牛,这段话对一般人来说太难理解了。那下面就试着解释一下为什么:

这就要涉及到C++对象的构造问题了,C++对象在三个地方构建:(1)函数堆栈;(2)自由存储区,或称之为堆;(3)静态存储区。无论在那里构建,其过程都是两步:首先,分配一块内存;其次,调用构造函数。好,问题来了,如果构造函数是虚函数,那么就需要通过vtable来调用,但此时面对一块 raw memeory,到哪里去找 vtable呢?毕竟,vtable 是在构造函数中才初始化的啊,而不是在其之前。因此构造函数不能为虚函数。

 

(2)析构函数可以是虚函数,且常常如此

这个就好理解了,因为此时 vtable 已经初始化了;况且我们通常通过基类的指针来销毁对象,如果析构函数不为虚的话,就不能正确识别对象类型,从而不能正确销毁对象。

 

 

困惑我们的是我们却经常看到“虚构造函数”这样的说法,这就要归咎于不负责任或者说误人子弟的媒体了(包括书、技术文章等等)。因为他们说的是类似下面这样的做法:

class Expr {

public:

     Expr();

     Expr(const Expr&);

     virtual Expr* new_expr() { return new Expr(); }

     virtual Expr* clone() { return new Expr(*this); }

};

 

问题一:虚析构函数与普通虚函数有什么区别

    首先,在C++中,每个类都只有一个析构函数,比如在子类Son中,便不能再添加父类father的虚函数virtual ~Father(void)的实现版本,这与普通虚函数是不同,普通虚函数是要在派生类中,重新给出父类中虚函数的实现,并且函数名相同(编译器实现的同名覆盖,会把所有析构函数解析为一个名字,如Destructor,故在子类中不会再添加父类的析构函数,因为子类有自己的析构函数,此时,若父类析构函数为虚函数,用子类对象的父类指针去访问父类析构函数其实就是多态,访问的为子类析构函数)。

问题二:虚析构函数的实现机制

   虚析构函数,也是通过vftable来实现的,实际上当父类的析构函数声明为虚函数时,子类的析构函数也变成了虚函数(虽然两者函数名不同),即告诉编译器,我和我的派生类都需要动态(运行时)完成析构函数执行。

 


基于RK3588与Gstreamer的端侧实时视频流AI推理系统构建 本文详细介绍了基于RK3588芯片与Gstreamer框架构建端侧实时视频流AI推理系统的完整方案。通过硬件选型、环境配置、OpenCV编译优化、视频流处理实战及模型部署等关键环节的深入解析,展示了如何利用RK3588的6TOPS NPU算力与Gstreamer的低延迟特性,实现工业质检、智能交通等场景下的高效边缘计算。特别针对RTSP推拉流优化、RKNN模型转换等核心技术难点提供了实用解决方案。 阅读详情

相关推荐

apm软件仿真+QGC地面站 环境搭建

本教程使用场景:apm软件仿真运行于虚拟机 ubuntu环境。qgc地面站位于windows 系统。两个环境在同一台电脑上。 一.apm仿真环境搭建 1.安装vm虚拟机及ubuntu 18。 2.ubuntu下 下载Ardupilot 飞控源码 并配置环境。 下载源码: git clone https://github.com/ArduPilot/ardupilot cd ardupilot git submodule update --init --recursive 配置所需运行的环境:

qthello 6110

虚析构函数的深度解析

析构函数为什么要写成虚函数,什么情况下写成虚函数,写成虚函数解决了什么问题, 解决问题的深层原理是什么? 前边都是表层,最后一个问题也困扰我了好一阵.. // Created by yanpan on 2019/5/10. #include <iostream> using namespace std; class Base { public: Base(){cout ...

NULL_NULL的博客 362

【VisualStdio】手把手教你做一个上位机(1)控件设计布局

网上有很多形形色色的制作上位机教程,有些写的挺好的,但是过于单调。

m0_73409202的博客 5437

虚析构函数详解

众所周知,virtual函数(虚函数),是实现动态调度(运行时多态) 概念:当基类指针指向子类对象时候,虚函数能实现运行时多态(多态指:同一个接口的不同实现方式) 虚析构函数:当基类指针指向指针对象的时候,需要把基类的析构函数设置成虚析构,防止内存泄露 1、当基类析构函数不设置为虚析构 class BaseClass { public: BaseClass() {} ~BaseCl...

qq_15821883的博客 1万+

深入浅出AXI协议(2)——通道及信号

本文我们继续AXI协议的学习,我们将讨论5个通道的具体内容和相对应的信号。这一部分的内容现在对于大家来说可能很难理解,但是没有关系,随着学习的深入,我们会逐步讲解这里面的所有现在,现在只需要把它们当作一个可以查阅的手册,快速阅读有一个基本的印象即可。

apple_53311083的博客 1799

v86.01 鸿蒙内核源码分析 (静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码

相比动态分配,静态内存池的分配就是个小弟弟,非常的简单,两个结构体 + 一张图 就能说明白。

日拱一卒,力出一孔 775

C++ 细节 彻底搞定深拷贝与浅拷贝

首先参考的是网友的讲非常不错: https://blog.youkuaiyun.com/weixin_41143631/article/details/81486817 浅拷贝:位拷贝,拷贝构造函数,赋值重载 多个对象共用同一块资源,同一块资源释放多次,崩溃或者内存泄漏 深拷贝:每个对象共同拥有自己的资源,必须显式提供拷贝构造函数和赋值运算符。 简而言之:深拷贝和浅拷贝可以简单理解为:如果一个类拥有...

weixin_42145502的博客 727

低功耗设计-ir drop的signoff corner怎么选择?

2.tt的(tt85 是比较接近芯片真实工作情况的);我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧?有要求就用要求的就好,没要求前两种看公司需求。3.signoff文档要求的;有几个方向,看公司需求吧。

m0_61544122的博客 1594

[C++]虚析构函数的作用

C++中的虚析构函数到底什么时候有用的,什么作用呢。 一.虚析构函数的作用 总的来说虚析构函数是为了避免内存泄露,而且是当子类中会有指针成员变量时才会使用得到的。也就说虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的。 我们知道,用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?下面用一个小...

wsq_zqfl的博客 1612

RK3399平台入门到精通系列讲解 - 总目录

欢迎大家来到内核笔记的《RK3399平台开发入门到精通系列讲解》,开始前博主先列出RK3399平台学习的大纲,同时这也可以作为大家学习RK3399内核与安卓框架的参考。下面蓝字都是传送门,点击进入即可:...

内核笔记 10万+

130、NPU的仿真测试:使用Gem5进行全系统仿真

这篇文章摘要(149字): 本文分享了使用Gem5全系统仿真器验证NPU设计的实践经验。作者通过搭建自定义NPU模型并集成到Gem5系统中,实现了CPU-NPU-内存协同仿真。关键点包括:1) 使用SimObject机制构建可配置的NPU模型;2) 合理配置IO和内存总线避免访问冲突;3) 开发Linux驱动实现CPU与NPU通信;4) 利用Gem5调试功能监控内部状态。相比RTL仿真和FPGA验证,该方法能快速验证微架构设计并定位系统级问题,有效减少了流片风险。文章特别强调了时钟域配置、总线仲裁、对齐访问

m0_50546716的博客 122

避免内存泄漏:C++ 虚析构函数指南

当基类的析构函数没有被声明为虚函数时,通过基类指针删除派生类对象会导致无法正确调用派生类的析构函数,从而可能引发内存泄漏等问题。 以上代码的输出是: 由于 的析构函数不是虚函数,所以通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的析构函数没有被调用,从而可能产生内存泄漏。当我们将基类的析构函数声明为虚函数时,通过基类指针删除派生类对象时,会正确调用派生类的析构函数,然后再调用基类的析构函数。 以上代码的输出是: 通过将基类的析构函数声明为虚函数(),我们确保了在释放派生类

m0_72877724的博客 752

stm32项目实战(1)光敏电阻的介绍和使用

文章目录前言一、光敏电阻介绍二、cubeMX配置三、代码说明总结 前言 本篇文章我们讲带大家了解光敏电阻的使用 一、光敏电阻介绍 下面是4线光敏电阻实物图 产品接线说明: 1、VCC 接电源正极 3.3-5V 2、GND 接电源负极 3、DO TTL 开关信号输出 (配置为输出模式) 4、AO 模拟信号输出(ADC采样通道) 光敏电阻主要用于检测周围的光照强度,光照强度发生变化那么ADC采样的值也会发送变化。 二、cubeMX配置 简单的配置我们就不多说了主要是ADC采样的配置 这里我采用的是ADC1通道

m0_49476241的博客 1万+

【C++学习笔记(二十)】之虚析构函数与纯虚析构函数

一. 虚析构函数        子类继承了父类,但是普通析构函数是不会调用子类的析构的,所以可能会导致内存释放地不干净。我们需要使用虚析构函数来解决。        写法:               Virtual ~Animal(){}        虚析构函数是为了解决基类的

开发小鸽 798

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

虚析构函数【C++进阶每日一学】

本文详细讲解C++中修析构函数的作用场景、必要性、原理以及使用方式,帮助你在C++中的知识更进一步

不会C++、害怕嵌入式且不爱看动漫的小趴菜 1万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【C++虚析构函数深度解析】:为何纯虚析构函数必须定义?揭秘背后原理

掌握C++虚析构函数的纯虚情况,解决派生类对象销毁不彻底问题。适用于多态基类设计,必须定义纯虚析构函数以确保正确调用各级析构逻辑,避免资源泄漏。原理清晰,实现安全,值得收藏。

ProcePerch的博客 940

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

虚析构函数解析

1     C++ 指出:当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则结果是未定义的。运行时比较有代表性的后果是对象的派生部分不会被销毁。如果一个类要被另外一个类继承,而且用其指针指向其子类对象时,如A* d = new B();(假定A是基类,B是从A继承而来的派生类),那么其(A类)析构函数必须是虚的,否则在delete d时,B类的析构函数将不会被调用

yinyuzw的专栏 257

c++ 多态 虚函数 析构函数 抽象类

1. 析构函数和虚析构函数      如果基类的析构函数是虚的,那么它的派生类的析构函数都是虚的       这将导致:当派生类析构的时候,它的所有的基类的析构函数都将得到调用       否则,只调用派生类的析构函数(这可能导致基类的某些对象没有得到释放)          所以CObject类的析构函数是虚的,所有由它派生的类析构的时候一级一级的进行,不会造成内存泄漏。     无论基类的

tattarrattat的专栏 6599

Cpp-虚函数

虚函数,虚析构函数,纯虚函数,抽象类 虚函数 定义:在函数前加virtual,例子void virtual demo(); 作用:多态的重要部分 一般来说父类中的函数被子类函数重写必须采用虚函数。 class Shape { public: virtual double calcArea(){...}//虚函数 ....

Cutepingping的博客 3250

C++多态与虚函数

文章目录C++多态和虚函数快速入门教程借助引用也可以实现多态多态的用途C++虚函数注意事项以及构成多态的条件构成多态的条件什么时候声明虚函数C++虚析构函数的必要性C++纯虚函数和抽象类详解关于纯虚函数的几点说明C++虚函数表精讲教程,直戳多态的实现机制C++ typeid运算符:获取类型信息1) 原型:const char* name() const;2) 原型:bool before (const type_info& rhs) const;3) 原型:bool operator== (cons

qq_41854911的博客 1904

C++——虚函数、虚析构函数、纯虚函数、抽象类

二、虚析构函数 1、什么是虚析构函数? 2、虚析构函数的作用 3、 构造函数 4、为什么构造函数不能是虚函数? 5、构造函数和虚析构函数的联系 6、为什么程序员不能调构造函数,但是可以调用析构函数? 7、虚析构函数产生多态 三、纯虚函数 1、定义: 2、纯虚函数的一般格式 3、例题理解 四、抽象类 1、抽象类的定义 2、抽象类的主要作用 如有错误,敬请指正。您的收藏与点赞都是对我最大的鼓励和支持!

x20020402的博客 2198

c++的类 基类指针数组与虚析构函数(九)

虚析构函数 就是 向派生类传递的析构结构 虚函数 是从顶部向下传递 虚析构函数是从底部向上传递的 基类是顶层 #include <iostream> #include <string> using namespace std; class A{ public: A() = default; A(string s) :s{ s }{} virtual ~A(){ cout << "析构函数A\n"; } // 虚析构函数向下传递 virtual void pri

qq_44698445的博客 557

C++虚析构函数

在C++中,构造函数不能被声明为虚函数,但是析构函数可以被声明为虚函数。只要基类的析构函数被声明为虚函数了,则它的派生类的析构函数,无论是否使用virtual,都将自动被说明为虚函数。 当使用delete运算符和析构函数一起使用时,delete一个对象,则delete隐含对析构函数的调用,如果析构函数不是虚函数,则子类的对象的析构函数将不会被自动调用。  1 基类的

u012220823的专栏 577

C++面向对象多态之虚析构函数和纯虚函数

虚析构函数 如果存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数) delete父类指针(如下面代码的cat指针)时,才会调用子类的析构函数,保证析构的完整性 #include <iostream> using namespace std; struct Animal { virtual void speak() { cout << "Animal::speak()" << endl; } virtual void run() { c

Arithmetic-logic 374

多态与虚函数(C++每日一学)

本文深入解析了C++中虚函数与多态的核心机制。重点介绍了动态多态通过虚函数实现运行时绑定的原理,揭示了vtable和vptr的底层实现机制。文章还探讨了纯虚函数与抽象类的应用场景,展示了如何通过接口规范强制派生类实现特定功能。最后介绍了C++11引入的override和final关键字,帮助开发者避免常见错误并优化继承体系设计。全文通过丰富的代码示例,系统性地讲解了多态在C++中的实现与应用,为构建灵活可扩展的软件系统提供了核心技术支持。

不会C++、害怕嵌入式且不爱看动漫的小趴菜 2万+
上一篇: 浅析C++对象布局
下一篇: 第一章 Web MVC简介 —— 跟开涛学SpringMVC 博客分类: 跟开涛学SpringMVC webmvcjavaeespring跟开涛学SpringMVC Web MVC简介 1.1、We
_一帘幽梦_
博客等级 码龄17年 72粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值