5.构造函数和析构函数的虚函数问题
5.1 题目
C++中构造函数能不能是虚函数?析构函数为什么常常要写成虚的?
5.2 深入解析
问题一:构造函数能不能是虚函数?
答案:不能。
原因分析
虚函数之所以能实现”动态绑定”,是因为对象内部通常会有一个虚表指针(vptr),它指向该对象所属类型的虚函数表(vtable)。调用虚函数时,本质上要先通过对象里的这个虚表指针,找到正确的虚函数入口。
但是构造函数的职责,就是把对象构造出来。也就是说:
- 在构造函数开始执行之前,对象还没完全构造完成
- 虚表指针(vptr)还处在初始化过程中
- 此时无法通过 vptr 进行虚函数调用
核心矛盾:要想虚调用,先得有对象;而构造函数就是在创建这个对象本身。
如果强行声明会怎样
即使语法上尝试将构造函数声明为虚函数,编译器也会直接报错。因为这在逻辑上是自相矛盾的。
问题二:析构函数为什么要写成虚的?
答案:当父类指针可能指向子类对象并通过父类指针删除时,析构函数应该声明为虚函数。
场景说明
Base* ptr = new Derived();
delete ptr; // 如果 Base 的析构函数不是虚函数,会出问题
虚析构函数的作用
| 情况 | 析构函数是否为虚 | 执行结果 |
|---|---|---|
| Base 析构非虚 | 否 | 只调用 Base 的析构函数,Derived 的析构不执行 |
| Base 析构为虚 | 是 | 先调用 Derived 析构,再调用 Base 析构 |
内存泄漏风险
如果析构函数不是虚函数:
- 通过父类指针删除子类对象时,只会调用父类的析构函数
- 子类特有的资源(如动态分配的内存、文件句柄等)不会被释放
- 导致内存泄漏或资源泄漏
正确写法
class Base {
public:
virtual ~Base() { // 虚析构函数
// 释放 Base 的资源
}
};
class Derived : public Base {
public:
~Derived() override { // 自动为虚析构函数
// 释放 Derived 的资源
}
};
总结对比
| 函数类型 | 能否为虚 | 原因 |
|---|---|---|
| 构造函数 | 不能 | 对象未构造完成,vptr 尚未初始化 |
| 析构函数 | 可以且推荐 | 确保通过基类指针删除派生类对象时正确调用派生类析构 |
5.3 答题示例
构造函数不能是虚函数。虚函数依赖对象里的虚表指针(vptr)来实现动态绑定,但构造函数执行时,对象还没构造完成,vptr 还在初始化过程中。要想虚调用,先得有对象;而构造函数就是在创建对象本身,所以不能是虚函数。
析构函数常常写成虚函数,主要是为了通过父类指针删除子类对象时,能正确调用子类的析构函数。
如果父类析构不是虚函数,delete 父类指针时只会调用父类析构,子类的资源就不会被释放,导致内存泄漏。
所以当父类会被当成基类指针使用并且可能 delete 子类对象时,析构函数通常应该写成虚函数。
5.4 关键词联想
- 虚函数表(vtable)
- 虚表指针(vptr)
- 动态绑定
- 构造函数
- 析构函数
- 内存泄漏
- 基类指针
- 多态
- override
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com