C++在构造函数和析构函数中调用虚函数不构成多态
·
📌 核心结论
在构造函数和析构函数中调用虚函数不构成多态,无论该函数是否声明为 virtual,都会调用当前正在构造/析构的类的版本。
🔍 原因分析
1. 对象的动态类型是分阶段的
class Base {
public:
Base() {
func(); // 此时对象类型是 Base
}
virtual void func() { std::cout << "Base::func()" << std::endl; }
virtual ~Base() { func(); }
};
class Derived : public Base {
public:
Derived() {
func(); // 此时对象类型才是 Derived
}
void func() override { std::cout << "Derived::func()" << std::endl; }
~Derived() { func(); }
};
时间线:
new Derived() 过程:
├─ Base 构造 → 对象类型: Base → 调用 Base::func()
└─ Derived 构造 → 对象类型: Derived → 调用 Derived::func()
delete ptr 过程:
├─ Derived 析构 → 对象类型: Derived → 调用 Derived::func()
└─ Base 析构 → 对象类型: Base → 调用 Base::func()
2. 虚函数表指针(vptr)的动态切换
构造过程中 vptr 的变化:
┌──────────────────────────┐
│ 1. 分配内存 │
│ vptr = 未初始化 │
├──────────────────────────┤
│ 2. Base 构造函数 │
│ vptr → Base::vtable │ ⚠️ 指向 Base 的虚函数表
│ 调用 func() │
│ → 查表 → Base::func() │
├──────────────────────────┤
│ 3. Derived 构造函数 │
│ vptr → Derived::vtable│ ✅ 更新为 Derived 的虚函数表
│ 现在多态才生效 │
└──────────────────────────┘
析构过程(逆序):
┌──────────────────────────┐
│ 1. Derived 析构函数 │
│ vptr → Derived::vtable│ ✅ 还在 Derived
│ 调用 func() │
│ → Derived::func() │
├──────────────────────────┤
│ 2. Derived 析构结束 │
│ vptr → Base::vtable │ ⚠️ 退回 Base
├──────────────────────────┤
│ 3. Base 析构函数 │
│ vptr → Base::vtable │
│ 调用 func() │
│ → Base::func() │
└──────────────────────────┘
3. 安全性考虑(最重要的原因)
如果允许在构造/析构时多态调用,会导致严重问题:
class Base {
public:
Base() { init(); } // 假设能调用 Derived::init()
virtual void init() {}
};
class Derived : public Base {
std::vector<int> data; // ⚠️ 在 Base() 执行时还未构造
public:
void init() override {
data.push_back(42); // ❌ 崩溃!data 不存在
}
};
问题时间线:
1. new Derived()
2. 调用 Base()
3. Base() 中调用 init()
4. 【如果多态】→ 调用 Derived::init()
5. 访问 data 成员
6. ❌ data 还没构造 → 未定义行为
📊 完整示例
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base 构造" << std::endl;
func();
}
virtual void func() {
std::cout << "Base::func()" << std::endl;
}
virtual ~Base() {
std::cout << "Base 析构" << std::endl;
func();
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived 构造" << std::endl;
func();
}
void func() override {
std::cout << "Derived::func()" << std::endl;
}
~Derived() {
std::cout << "Derived 析构" << std::endl;
func();
}
};
int main() {
std::cout << "=== 创建对象 ===" << std::endl;
Base* ptr = new Derived();
std::cout << "\n=== 正常调用(多态) ===" << std::endl;
ptr->func(); // ✅ 多态:调用 Derived::func()
std::cout << "\n=== 销毁对象 ===" << std::endl;
delete ptr;
return 0;
}
输出:
=== 创建对象 ===
Base 构造
Base::func() ← Base() 中调用,非多态
Derived 构造
Derived::func() ← Derived() 中调用,非多态
=== 正常调用(多态) ===
Derived::func() ← ✅ 多态调用
=== 销毁对象 ===
Derived 析构
Derived::func() ← ~Derived() 中调用,非多态
Base 析构
Base::func() ← ~Base() 中调用,非多态
📋 对比表
| 调用场景 | 是否多态 | 调用版本 |
|---|---|---|
Base() 中调用 func() |
❌ 否 | Base::func() |
Derived() 中调用 func() |
❌ 否 | Derived::func() |
ptr->func() (完整对象) |
✅ 是 | Derived::func() |
~Derived() 中调用 func() |
❌ 否 | Derived::func() |
~Base() 中调用 func() |
❌ 否 | Base::func() |
💡 关键要点
-
对象类型分阶段
- 构造时:逐步"成为"派生类
- 析构时:逐步"退化"为基类
-
vptr 动态切换
- 构造:Base vtable → Derived vtable
- 析构:Derived vtable → Base vtable
-
安全第一
- 防止访问未构造的成员
- 防止访问已析构的成员
-
标准规定
- C++11 §12.7/3 明确定义此行为
- 构造/析构时调用的是当前类的版本
Q: 为什么不构成多态?
因为在构造/析构期间,对象的动态类型是当前正在构造/析构的类,而不是最终的派生类。虚函数表指针会在构造/析构过程中动态切换,确保只调用已构造部分的函数,这是为了安全性设计。
Q: 这是 bug 吗?
不是,这是 C++ 的设计特性。如果允许多态,会导致访问未构造或已销毁的成员,造成未定义行为。
Q: 如何避免这个问题?
- 不在构造/析构函数中调用虚函数
- 使用工厂方法在对象完全构造后初始化
- 使用两阶段初始化模式
更多推荐


所有评论(0)