导言
本文将介绍为什么要将多态性质基类的析构函数声明为虚函数,以及什么是纯虚析构函数。
virtual 析构函数
资源泄漏出现
有时我们希望借助父类指针释放子类对象::
class Person
{
public:
Person();
~Person();
...
};
class Student : public Person
{
... };
// 我们用基类指针指向派生类对象并释放
Person* ptr = new Student;
...
delete ptr;
C++明确指出,当派生类对象经由一个基类指针删除,而该基类的析构函数并不是虚函数,其结果是未定义的。
- 实际执行时通常发生的是对象的派生成分没被销毁
- 也就是说声明于
Student
类内的成员变量没被销毁 - 然而其基类成分会被销毁,于是造成了诡异的资源泄漏
解决
解决这个问题的方法很简单,只需要将基类的析构函数声明为虚函数即可。这样delete
基类指针时,会销毁整个对象,包括所有的派生成分。
虚函数:即被
virtual
修饰的类成员函数称为虚函数。
class Person
{
public:
Person();
virtual ~Person();
...
};
class Student : public Person
{
... };
Person* ptr = new Student;
...
delete ptr;
此时,当调用析构函数时,首先会找到对象内的虚表指针,在虚表指针所指向的虚表中找到适当的函数指针并调用。
总结:
- 带多态性质的基类应该声明一个
virtual
析构函数。如果基类带有任何virtual
函数,它就应该拥有一个virtual
析构函数 - 类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明为
virtual
析构函数
pure virtual析构函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类,抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。
常规纯虚函数
因为包含纯虚函数的类不能实例化出对象,所以纯虚函数一般没有函数体。
- 没有对象无法调用该纯虚函数,因此实现没有价值,通常不实现
或许你会想到用类类型的指针来调用,下面就来看一下这种方法:
class Animal
{
public:
virtual void Run() = 0;
void Fly();
};
void Animal::Run() {
cout << "Run()" << endl; }
void Animal::Fly() {
cout << "Fly()" << endl; }
Animal* ptr = nullptr;
ptr->Fly(); // 程序正常屏幕打印Fly()
ptr->Run(); // 程序崩溃屏幕不打印
为什么会出现上面的情况?普通函数和纯虚函数有什么不同吗?
这是因为虚函数的调用需要通过虚表指针,没有对象也就没有虚表指针,故上述代码会崩溃。
纯虚析构
有时我们需要一个抽象类,但又没有纯虚函数,该怎么办呢?由于抽象类总是被当作基类来使用,而基类又应该有个virtual
析构函数,并且由于纯虚函数会产生抽象类,因此我们可以:把希望成为抽象的那个类声明一个pure virtual 析构函数。
class Animal
{
public:
virtual ~Animal() = 0;
};
// 必须为这个pure virtual函数提供一份定义,否则连接器会报错
Animal::~Animal() {
}
析构函数的运行逻辑是,最深层派生的呢个类其析构函数最先被调用,然后是其每一个基类的析构函数被调用。编译器会在Animal
的派生类的析构函数中创建一个对~Animal
的调用动作,所以必须为这个函数提供一份定义。否则,连接器会报错。
转载:https://blog.csdn.net/qq_40080842/article/details/125467491