一、示例代码
#include<iostream>
using namespace std;
class Weapon {
public:
virtual void getWeapon() = 0;
//virtual ~Weapon() = default;
};
class bow :public Weapon
{
public:
void getWeapon() override
{
cout << "弓箭 " << ends;
}
~bow()
{
cout << "弓箭损坏了!" << endl;
}
};
class gun :public Weapon
{
public:
void getWeapon() override
{
cout << "火枪 " << ends;
}
~gun()
{
cout << "火枪损坏了!" << endl;
}
};
void weapon_delete(Weapon* test)
{
delete test;
}
int main(int argc,char** argv)
{
Weapon* my_bow = new bow();
weapon_delete(my_bow);
Weapon* my_gun = new gun();
weapon_delete(my_gun);
return 0;
}
执行上面示例代码我们发现:
并未执行我们预期的析构函数
如果解开Weapon
类中的virtual ~Weapon() = default;
我们发现,符合我们的预期:
我们在基类中引入了虚析构,达到了我们的目标。来看看虚析构有啥用吧!
二、引入虚析构前后各类虚函数表结构
我们来观察下引入虚析构前后各类的虚函数表中的结构
参考链接:如何通过vs查看类的内存结构
引入前:
引入后:
)
结论:
引入虚析构之后,我们发现派生类bow
的虚函数表中,多了一个析构函数!
二、为何引入虚析构?
引入虚析构函数的主要目的是解决对象生命周期管理中的问题,尤其是在多态和继承的上下文中。下面是引入虚析构函数的几个重要原因:
1. 正确清理资源
在使用多态时,通常会通过基类指针或引用来操控派生类对象。当基类对象被删除时,如果基类的析构函数不是虚拟的,仅会调用基类的析构函数。这将导致派生类未释放的资源(如动态分配的内存、打开的文件句柄等)无法正确清理,可能引发内存泄漏或其他资源泄漏问题。
2. 保障对象完整性
虚析构函数确保在删除对象时遵循一个完整的析构顺序:先调用派生类的析构函数,再调用基类的析构函数。这种机制有助于确保对象的完整性和正确的资源管理,避免未定义行为。
3. 提高代码的可维护性
当类设计包括多态性时,正确实现虚析构函数使得代码更加模块化、可重用和可扩展。这样,通过基类指针传递的对象能够安全地在运行时处理其实际类型的析构逻辑,降低了手动管理内存和资源的复杂性。
4. 支持复杂的继承结构
在复杂的多层继承结构中,确保基类的析构函数为虚函数是很重要的。这样,任何派生类都可以正确地释放其资源,维护层次结构的完整性。
示例
以下是一个简单的示例,说明如果不使用虚析构函数,可能导致的问题:
#include <iostream>
using namespace std;
class Base {
public:
~Base() { cout << "Base Destructor called" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived Destructor called" << endl; }
};
void function(Base* obj) {
delete obj; // 如果 Base 的析构函数不是虚拟的
}
int main() {
Base* obj = new Derived();
function(obj);
// 将只调用 Base 的析构函数,导致 Derived 的析构函数不会被调用
return 0;
}
在这个例子中,由于 Base
的析构函数不是虚拟的,即使 obj
实际上是一个 Derived
对象,Derived
的析构函数也不会被调用,从而导致资源泄漏。
总结
总的来说,虚析构函数的引入是为了确保在从基类指针删除派生类对象时能够正确调用派生类的析构函数,以便完成资源清理,维护程序的健壮性和安全性。通过这一机制,C++ 提供了一种可靠的方式来管理多态性带来的复杂性,增强了面向对象编程的灵活性和可维护性。
三、总结
虚析构作用:通过基类指针、引用 释放 子类的所有空间。