c++虚析构
内存泄漏问题#
class BaseA {
public:
BaseA() {
printf("BaseA::BaseA()\n");
};
~BaseA() {
printf("BaseA::~BaseA()\n");
};
virtual void func1() {};
};
class BaseC : public BaseA {
public:
BaseC() {
printf("BaseC::BaseC()\n");
};
~BaseC() {
printf("BaseC::~BaseC()\n");
};
virtual void func3() {};
};
int main()
{
BaseA* test = new BaseC();
delete test;
return 0;
}
上述代码打印如下,也就是并未调用继承类BaseC
的析构函数,如果BaseC
中析构函数中存在资源释放操作的话就会造成内存泄漏。
BaseA::BaseA()
BaseC::BaseC()
BaseA::~BaseA()
让基类BaseA
声明为虚函数后就可以解决这个问题,基类BaseA
声明为虚函数后编译器会创建代理析构函数放在虚表中,delete 基类指针的时候就会调用虚表中的代理析构函数BaseC::'scalar deleting destructor'(unsigned int)
,代理析构会分别调用BaseC
和BaseA
的析构函数。
此时test
对象的内存布局如下
崩溃或未知问题#
class BaseA {
public:
BaseA() {
printf("BaseA::BaseA()\n");
};
~BaseA() {
printf("BaseA::~BaseA()\n");
};
virtual void func1() {};
};
class BaseB {
public:
BaseB() {
printf("BaseB::BaseB()\n");
};
~BaseB() {
printf("BaseB::~BaseB()\n");
};
virtual void func2() {};
};
class BaseC : public BaseB, public BaseA {
public:
BaseC() {
printf("BaseC::BaseC()\n");
};
~BaseC() {
printf("BaseC::~BaseC()\n");
};
virtual void func3() {};
};
int main()
{
BaseA* test = new BaseC();
delete test;
return 0;
}
多层继承中未定义虚析构造成内存泄漏的问题这种场景是比较常见的。不过还有一种不常见的场景,就是多重继承下未定义虚析构带来的崩溃和其他未知问题。例子中BaseC
同时继承了BaseB和BaseA
,定义一个BaseC
对象并用BaseA
基类指针保存。此程序运行会出现崩溃问题,看一下崩溃堆栈是调用delete test
-->free
崩溃的。
看一下当前内存的一个布局,可以发现此时BaseA *test
基类指针指向的是整个BaseC
对象中BaseA
对象的起始地址。这就造成了调用free的时候传入的地址不是BaseC
对象的起始地址,因为new/malloc
申请内存的时候会将申请内存的大小以一定格式保存在申请基地址的前面(可能是以结构体的形式保存,这是由编译器决定的,每个编译器都不一样),这样free
再释放内存的时候才能去索引到结构并确定释放多少内存,如果传给free
的起始地址不对,其索引得到的需要释放的内存大小有可能就是错误的,就可能造成程序的崩溃。
但是如果定义成BaseB* test = new BaseC()
,也就是将new的BaseC
对象保存在最先继承的类的基类指针中,在delete的时候就不会有这种问题,因为此时test
确实是指向了BaseC
对象中BaseB
对象的起始地址,刚好其内存布局就在BaseC
对象的起始位置,这样free
传入的起始地址就不会崩溃。
最终的解决办法就是基类BaseA
和BaseB
的析构函数定义为虚析构,这样编译器就会创建一个代理析构保存在虚表中,代理析构会对传给free的地址进行自动调整。例如本来BaseA* test = new BaseC()
时test
指针的位置并不在BaseC
对象的头部,而是跳过了BaseB
对象并指向了BaseA
对象,代理析构BaseC::'scalar deleting destructor'(unsigned int)
在调用free
前会对test
指针的值进行修正,因为这里BaseB
对象没有成员只有一个虚表,所以修正的时候就是减去一个虚表地址的大小4
基类析构函数设置成虚函数后整个内存布局变化如下
最后总结为一句话:析构函数一定要设置成虚函数,避免不必要的问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】