【c++】多态中的构造函数和析构函数
一、构造函数
1. 构造函数的核心任务
1.1构造函数负责 初始化对象的成员变量 和 设置虚表指针(vptr)。
- 虚表指针(vptr):当一个类包含虚函数时,编译器会隐式地为该类的每个对象插入一个指向虚表(vtable)的指针(vptr)。
- 虚表(vtable):存储该类所有虚函数地址的静态表,在编译时生成,每个类唯一。
1.2构造函数负责
- 1创建对象:构造函数在对象被实例化时自动调用,负责分配内存。
- 2初始化成员:通过初始化列表或函数体为对象的成员变量赋初始值。
- 3支持隐式类型转换:若构造函数只有一个参数(或其余参数有默认值),可隐式转换参数类型为类类型。
2. 构造函数的执行顺序
对象构造按 基类 → 派生类 的顺序进行,vptr 的初始化也遵循这一规则:
- 基类构造阶段:初始化基类成员,vptr 指向基类的虚表。
- 派生类构造阶段:初始化派生类成员,vptr 被更新为指向派生类的虚表。
ps:构建派生类对象时首先构造匿名的基类对象(前面提到过)此时调用基类的构造函数,使得虚函数指针指向基类的虚函数表,接着调用派生类的构造函数,重置虚表指针指向派生类的虚表
class Base {
public:Base() { // 此时 vptr 指向 Base 的虚表}virtual void func() { /*...*/ }
};class Derived : public Base {
public:Derived() : Base() { // 此时 vptr 更新为指向 Derived 的虚表}void func() override { /*...*/ }
};
3. 为什么构造函数不能是虚函数?
- 对象未完全构造:在构造函数执行时,对象尚未完全构造,vptr 可能未正确初始化。
- 静态绑定:构造函数调用在编译时确定类型(如
new Derived()
)属于静态联编 - 逻辑矛盾:虚函数依赖 vptr,但 vptr 在构造函数中初始化,形成循环依赖。
- 构造函数调用是编译时确定,如果为虚构造函数,编译器无法确认你够简单类型
二、析构函数与虚析构函数
1. 析构函数的核心任务
析构函数负责 释放对象资源 和 重置 vptr(编译器隐式处理)。
2. 虚析构函数的重要性
若基类指针指向派生类对象,派生类在堆区分配空间,非虚析构函数会导致内存泄漏:
class Base {
public:~Base() { cout << "Base destroyed" << endl; } // 非虚析构函数
};class Derived : public Base {
public:~Derived() { cout << "Derived destroyed" << endl; }
};Base* obj = new Derived();
delete obj; // 仅调用 Base 的析构函数,Derived 的析构函数未执行!
在delete obj指针时 这里使用的是静态联编的方式 根据obj类型直接调用了base析构函数
解决方法:将基类析构函数声明为虚函数:
virtual ~Base() { ... }
此时 delete obj
会触发动态绑定,正确调用 Derived::~Derived()
。
3. 虚析构函数的执行顺序
析构按 派生类 → 基类 顺序执行:
- 派生类析构函数执行,vptr 指向派生类的虚表。
- 基类析构函数执行,vptr 被重置为指向基类的虚表。
三、关键点总结
特性 | 构造函数 | 析构函数 |
---|---|---|
能否为虚函数 | ❌ 不能 | ✅ 必须为虚(若可能被继承) |
vptr 初始化/重置 | 初始化(指向当前类的虚表) | 重置(指向当前类的虚表) |
多态行为 | 静态绑定(编译时确定类型) | 动态绑定(需虚析构函数支持) |
资源管理 | 分配资源(如内存、文件句柄) | 释放资源 |
四、代码示例:完整生命周期
#include <iostream>
using namespace std;class Animal {
public:Animal() { cout << "Animal constructed" << endl; }virtual ~Animal() { cout << "Animal destroyed" << endl; } // 虚析构函数virtual void speak() { cout << "Animal sound" << endl; }
};class Dog : public Animal {
public:Dog() { cout << "Dog constructed" << endl; }~Dog() override { cout << "Dog destroyed" << endl; }void speak() override { cout << "Woof!" << endl; }
};int main() {Animal* animal = new Dog(); // 构造顺序:Animal → Doganimal->speak(); // 动态绑定,输出 "Woof!"delete animal; // 析构顺序:Dog → Animalreturn 0;
}
输出:
Animal constructed
Dog constructed
Woof!
Dog destroyed
Animal destroyed