先看一个问题,为什么基类的析构函数应该声明为virtual?
class Base {
public:
~Base() { cout << "Base destructor" << endl; } // 非虚析构函数
};
class Derived : public Base {
public:
~Derived() { cout << "Derived destructor" << endl; }
};
int main() {
Base* obj = new Derived();
delete obj; // 猜猜会输出什么?
}
▶ 你的预测结果是?实际运行后会发现:
- 仅输出
Base destructor Derived的析构函数没有被调用!
这就是问题的本质。
-
当通过基类指针删除派生类对象时,如果析构函数不是虚函数:
- 编译器根据指针类型(
Base*)决定调用哪个析构函数 - 派生类的析构函数被跳过 → 资源泄漏
- 编译器根据指针类型(
-
正确的做法:
class Base {
public:
virtual ~Base() { cout << "Base destructor" << endl; } // 虚析构函数
};
此时输出:
Derived destructor
Base destructor
深层原理:
- 虚函数表(vtable)机制保证正确调用实际对象的析构函数
- 析构函数的调用顺序:派生类 → 基类(与构造顺序相反)
底层原理(编译器行为):
// 伪代码展示delete obj时的实际操作
if (obj != nullptr) {
// 1. 通过虚函数表找到实际对象的析构函数
void (*dtor)(void*) = obj->vptr[-1];
// 2. 先调用派生类析构函数
dtor(obj);
// 3. 再调用operator delete释放内存
operator delete(obj);
}
重要推论:
- 如果一个类可能被继承(即使现在没有),且会通过基类指针操作对象:
⇒ 必须声明虚析构函数 - 但不需要所有类都加virtual(会带来额外开销)
再来看这个问题:如果基类是抽象类(含纯虚函数),但忘记给纯虚析构函数提供实现,会发生什么?
这个问题非常具有陷阱性!先看这个看似正常的代码:
class AbstractBase {
public:
virtual ~AbstractBase() = 0; // 纯虚析构函数声明
virtual void foo() = 0; // 其他纯虚函数
};
class Concrete : public AbstractBase {
public:
~Concrete() override { cout << "Concrete destroyed" << endl; }
void foo() override {}
};
int main() {
AbstractBase* obj = new Concrete();
obj->foo();
delete obj; // 这里会发生什么?
}
▶ 运行时会直接崩溃,错误通常是:
undefined reference to `AbstractBase::~AbstractBase()'
根本原因:
- 纯虚析构函数仍然需要实现(与其他纯虚函数不同!)
- 析构函数调用链的机制决定了:
- 即使派生类实现了自己的析构函数
- 编译器仍会尝试调用基类的析构函数
内存中的真实调用顺序(伪代码):
// 当执行 delete obj 时:
1. 调用 Concrete::~Concrete()
2. 调用 AbstractBase::~AbstractBase() // 如果这里没有实现→链接错误
3. 调用 operator delete()
正确写法:
class AbstractBase {
public:
virtual ~AbstractBase() = 0; // 纯虚声明
};
// 必须提供实现(通常在.cpp文件中)
AbstractBase::~AbstractBase() { /* 可空实现 */ }
关键区别:
| 特性 | 普通纯虚函数 | 纯虚析构函数 |
|---|---|---|
| 是否需要实现 | 派生类必须实现 | 基类和派生类都要实现 |
| 可否内联实现 | 不可以 | 可以 |
| 典型用途 | 强制接口实现 | 强制抽象基类+清理资源 |

被折叠的 条评论
为什么被折叠?



