C++的虚析构函数
1.语法规则:
virtual ~析构函数 // 写了虚析构就不能同时再去写普通析构
{
}
父类的指针指向子类对象的时候,如果delete释放父类的指针,那么正常情况下只会调用父类的析构函数,不会调用子类的析构函数(释放不彻底)
2.用途:
把父类的析构函数定义成虚析构就能解决
在继承的时候建议把父类的析构函数定义成虚析构
3.原理:
- 不加virtual,此时采用静态联编(只调用赋值运算左边的类(父类)析构函数)
Cat c1;
Animal *p=c1; // 父类指针指向子类对象
delete p;
- 加virtual,此时采用动态联编(依据赋值运算右边的类型(子类),先调用子类析构,再调用父类析构)
Cat c1;
Animal *p=c1; // 父类指针指向子类对象
delete p;
示例代码:
#include <iostream>
using namespace std;
/*
虚析构为了解决特定的问题,发明的一种语法规则。
特定的问题:
父类的指针指向子类的堆空间,delete父类指针,默认情况下只会调用父类的析构,不会调用子类的析构
解决方法:
把父类的析构函数定义成虚析构
*/
class Animal
{
public:
Animal()
{
cout<<"父类动物构造"<<endl;
}
virtual ~Animal()
{
cout<<"父类动物析构"<<endl;
}
};
class Cat:public Animal
{
public:
Cat()
{
cout<<"子类猫构造"<<endl;
}
~Cat()
{
cout<<"子类猫析构"<<endl;
}
};
// void showAnimalEat(Animal *other)
// {
// other->eat();
// delete other;
// }
// Cat *c1=new Cat;
// Dog *d1=new Dog;
// showAnimalEat(c1);
// showAnimalEat(d1);
int main()
{
Cat *c1=new Cat;
//父类的指针指向子类对象
Animal *p=c1;
//释放堆空间
// delete c1;
delete p; //加了virtual,析构就正常了,不加virtual析构不彻底
/*
不加virtual只会调用基类的析构函数而不调用子类的析构函数:
父类动物构造
子类猫构造
父类动物析构
*/
}
/*
执行结果: 在基类中加了virtual后不管是delete子类指针还是delete父类指针,都会调用子类的析构函数
父类动物构造
子类猫构造
子类猫析构
父类动物析构
*/
4. 下面解释为什么基类未定义为析构函数时,析构子类(派生类)对象也能把基类对象析构的原因
\quad 在C++中,当直接通过派生类指针删除对象时,派生类析构函数能够自动调用基类析构函数的原理,主要基于C++语言标准规定的对象销毁顺序
和编译器生成的隐式代码
机制。以下是详细解释:
4.1核心原理:编译器自动生成的析构函数调用链
4.1.1 对象构造与析构的镜像对称原则
- 构造顺序:基类构造 → 成员构造 → 派生类构造
- 析构顺序:派生类析构 → 成员析构 → 基类析构(完全相反)
4.1.2 编译器在派生类析构函数中插入隐式代码
\quad 当编译器处理派生类析构函数时,会自动在函数体末尾添加调用基类析构函数的代码。例如:
// 用户编写的派生类析构函数
~Derived() {
// 用户定义的清理代码
}
// 编译器实际生成的代码
~Derived() {
// 1. 用户定义的清理代码
// 2. 析构所有成员对象(按声明逆序)
// 3. 调用直接基类的析构函数 <-- 关键!
}
4.2 底层机制分步解析
假设有以下类结构:
class Base {
public:
~Base() { /* 基类析构 */ }
};
class Derived : public Base {
Member m; // 成员对象
public:
~Derived() { /* 派生类析构 */ }
};
当执行 delete d(Derived* d)时:
- 调用派生类析构函数
2) 执行用户代码
// 执行用户编写的析构代码
3) 析构成员对象(编译器插入)
// 编译器自动添加成员析构
m.~Member(); // 逆序析构所有成员
4)调用基类析构函数(编译器插入)
// 编译器自动添加基类析构调用
Base::~Base(this); // 关键步骤!
5)释放内存
operator delete(d); // 释放整个对象内存
4.3 技术细节说明
4.3.1.this指针调整
- 当调用基类析构函数时,编译器会自动将this指针调整到基类子对象的起始位置
- 例如:Base::~Base(this + offset) → 实际是 Base::~Base( (Base*)this )
4.3.2.继承链处理
对于多级继承:
class GrandBase { /*...*/ };
class Base : public GrandBase { /*...*/ };
class Derived : public Base { /*...*/ };
编译器生成的 ~Derived() 会:
~Derived() {
// 用户代码
// 析构成员
Base::~Base(this); // 调用直接基类
// 实际在~Base()中会再调用GrandBase::~GrandBase()
}
4.3.3.与虚函数无关
此过程完全在编译时确定:
- 不依赖虚函数表(vtable)
- 不涉及运行时动态查找
- 是静态绑定的函数调用