Effective C++ 条款07:为多态基类声明virtual析构函数
核心思想:当通过基类指针删除派生类对象时,如果基类没有虚析构函数,会导致派生类资源泄漏。因为此时只会调用基类的析构函数,而不会调用派生类的析构函数。
⚠️ 1. 问题场景:非虚析构函数导致资源泄漏
class Base {
public:Base() { std::cout << "Base构造\n"; }~Base() { std::cout << "Base析构\n"; } // 非虚析构函数
};class Derived : public Base {
public:Derived() : data(new int(42)) { std::cout << "Derived构造\n"; }~Derived() { delete data; // 释放资源std::cout << "Derived析构\n"; }
private:int* data; // 派生类独占资源
};int main() {Base* pb = new Derived(); // 基类指针指向派生类对象delete pb; // 仅调用Base::~Base() → 内存泄漏!
}
输出结果:
Base构造
Derived构造
Base析构
问题:Derived
的资源data
未被释放 → 内存泄漏!
✅ 2. 解决方案:声明虚析构函数
class Base {
public:Base() { std::cout << "Base构造\n"; }virtual ~Base() { std::cout << "Base析构\n"; } // 虚析构函数
};class Derived : public Base { /* 实现同上 */ };int main() {Base* pb = new Derived();delete pb; // 正确调用派生类析构函数
}
输出结果:
Base构造
Derived构造
Derived析构 // 先调用派生类析构函数
Base析构 // 再调用基类析构函数
🔍 3. 关键原则
场景 | 析构函数要求 | 原因 |
---|---|---|
多态基类(有虚函数) | 必须为virtual | 确保通过基类指针删除派生类对象时,正确调用派生类析构函数 |
非多态基类(无虚函数) | 不应为virtual | 避免虚表指针带来的空间开销(条款7指出每个对象增加4-8字节) |
STL容器(如std::string ) | 禁止继承 | 标准库类的析构函数均为非虚,通过基类指针删除派生类对象会导致未定义行为 |
⚠️ 4. 错误实践:继承STL容器类
class MyString : public std::string {
public:~MyString() { std::cout << "MyString析构\n"; }
};int main() {std::string* ps = new MyString(); delete ps; // 未定义行为!std::~string非虚
}
结果:MyString::~MyString()
不会被调用 → 潜在资源泄漏!
💎 5. 纯虚析构函数的特殊用法
使类成为抽象类,同时仍需要提供实现
class AbstractBase {
public:virtual ~AbstractBase() = 0; // 纯虚声明
};
AbstractBase::~AbstractBase() {} // 必须提供实现class Concrete : public AbstractBase {
public:~Concrete() override { std::cout << "Concrete析构\n"; }
};int main() {AbstractBase* p = new Concrete();delete p; // 正确调用链:Concrete::~ → AbstractBase::~
}
总结:多态基类虚析构三原则
- 多态基类必须声明虚析构函数
virtual ~Base() = default;
- 非多态基类不要声明虚析构函数
避免无谓的虚函数表开销 - 禁止继承无虚析构函数的类(如STL容器)
组合优于继承:将目标类作为成员变量而非基类