为什么析构函数一般写成虚函数?

在 C++ 中,如果一个类有虚函数,它的析构函数通常应该也是虚函数,这样可以确保在 删除基类指针指向的派生类对象时,派生类的析构函数能够正确调用,避免 内存泄漏或未释放的资源。

1. 非虚析构函数的问题

如果基类的析构函数 不是虚函数,当基类指针指向派生类对象,并通过基类指针删除对象时,只会调用基类的析构函数,不会调用派生类的析构函数,导致 派生类的资源没有释放,造成内存泄漏。

错误示例(析构函数不是虚函数)

#include

using namespace std;

class Base {

public:

Base() { cout << "Base Constructor\n"; }

~Base() { cout << "Base Destructor\n"; } // ❌ 不是虚函数

};

class Derived : public Base {

public:

Derived() { cout << "Derived Constructor\n"; }

~Derived() { cout << "Derived Destructor\n"; }

};

int main() {

Base* ptr = new Derived(); // 基类指针指向派生类对象

delete ptr; // ❌ 只会调用 Base 的析构函数,Derived 的析构函数不会调用

return 0;

}

输出

Base Constructor

Derived Constructor

Base Destructor // ⚠️ 只调用了 Base 的析构函数,Derived 的没有调用

问题

delete ptr; 只调用了 Base 的析构函数,而 没有调用 Derived 的析构函数。这可能导致 Derived 里的动态分配资源 没有释放,引起 内存泄漏。

2. 正确做法:将析构函数设为 virtual

如果基类的析构函数是 virtual,那么当 delete 一个基类指针指向的派生类对象时,派生类的析构函数会被正确调用。

正确示例(析构函数是虚函数)

#include

using namespace std;

class Base {

public:

Base() { cout << "Base Constructor\n"; }

virtual ~Base() { cout << "Base Destructor\n"; } // ✅ 设为虚函数

};

class Derived : public Base {

public:

Derived() { cout << "Derived Constructor\n"; }

~Derived() { cout << "Derived Destructor\n"; }

};

int main() {

Base* ptr = new Derived(); // 基类指针指向派生类对象

delete ptr; // ✅ 正确调用派生类的析构函数

return 0;

}

正确输出

Base Constructor

Derived Constructor

Derived Destructor // ✅ 先调用 Derived 的析构函数

Base Destructor // ✅ 再调用 Base 的析构函数

为什么正确?

由于 Base::~Base() 是 虚函数,delete ptr; 会 动态绑定 到 Derived 的析构函数。先调用 Derived 的析构函数,然后调用 Base 的析构函数,确保 所有资源都被正确释放。

3. 适用于多态的类

如果一个类被 设计成基类(用于继承),并且它包含 虚函数(比如 virtual func()),那么它的 析构函数也应该是虚函数。

class Shape {

public:

virtual void draw() = 0; // 纯虚函数,表示该类是抽象基类

virtual ~Shape() {} // ✅ 确保正确析构派生类对象

};

任何 包含虚函数的基类,它的析构函数通常 都应该是虚函数。

4. virtual 可能带来的额外开销

虚函数表(vtable):

在有 virtual 函数的类中,C++ 维护一个 虚函数表(vtable),用于动态绑定虚函数。这样会稍微增加 内存占用 和 运行时调用开销。但通常这个开销 是可以接受的,特别是在有 多态 需求时。 不需要继承的类,不要使用 virtual 析构函数:

如果一个类 不会被继承,不需要 virtual ~ClassName(),否则会 增加不必要的 vtable 负担。例如:class NonPolymorphic {

public:

~NonPolymorphic() {} // ❌ 这里不需要 virtual

};

5. virtual 析构函数 vs override

有时候,我们还可以 显式声明析构函数为 override,以确保它正确覆盖基类的析构函数。

class Base {

public:

virtual ~Base() {} // ✅ 确保派生类析构函数被调用

};

class Derived : public Base {

public:

~Derived() override {} // ✅ 显式声明 override,确保正确覆盖

};

override 不是必须的,但可以 提高代码可读性,避免拼写错误导致的未覆盖。

6. 什么时候不需要 virtual 析构函数?

如果 类不是用于继承的(不是基类),那么 不需要 virtual 析构函数,否则会 增加不必要的开销。

class NonPolymorphic {

public:

~NonPolymorphic() {} // ❌ 这里不需要 virtual

};

但是,如果 类有虚函数(如 virtual void func();),那么它 很可能被继承,此时应该 加 virtual 析构函数。

7. 总结

✅ 如果类是基类(用于继承),并且包含虚函数,它的析构函数通常应该是 virtual。 ✅ 如果基类的析构函数不是 virtual,使用 delete 基类指针时,不会调用派生类析构函数,可能导致内存泄漏。 ✅ 如果类不会被继承,则不需要 virtual 析构函数,以避免额外的 vtable 负担。 ✅ 如果有 virtual 函数(多态行为),那么析构函数也应该 virtual,确保正确释放派生类资源。

最佳实践

基类(用于继承)应该这样写:

class Base {

public:

virtual ~Base() {} // ✅ 确保派生类对象正确析构

};

如果类不打算被继承,则不要使用 virtual,减少 vtable 开销:

class NonPolymorphic {

public:

~NonPolymorphic() {} // ❌ 这里不需要 virtual

};

希望这个解释能帮助你理解 为什么析构函数一般写成 virtual!