一、虚析构与纯虚析构的共同点:
- 都是释放子类的堆区资源。
- 都需要具体实现。
二、虚析构与纯虚析构的不同点:
- 使用纯虚析构函数的父类属于抽象类,不能实例化具体对象。代码示例如下:
#include <iostream> // 父类 class Base { public: // 纯虚析构函数 virtual ~Base() = 0; }; // 纯虚析构函数的实现,此处必须实现,因为是析构函数,可能会释放父类的堆区资源,否则代码会报错 Base::~Base() { std::cout << "父类的纯虚析构函数调用" << std::endl; } class Son : public Base { public: // 子类析构函数的实现 virtual ~Son() { std::cout << "子类的析构函数调用" << std::endl; } }; int main() { // 实例化失败,此处代码会报错,因为父类有纯虚析构函数,是抽象类 // Base * base = new Base; // 实例化成功,因为子类不是抽象类 Base * base = new Son; system("pause"); return 0; }
- 使用虚析构函数的父类不属于抽象类,可以实例化具体对象。代码实例如下:
#include <iostream> class Father { public: // 父类的虚析构函数 virtual ~Father() { std::cout << "父类的虚析构函数调用" << std::endl; } }; int main() { // 此时父类实例化成功,因为父类不是抽象类,代码不会报错 Father * father = new Father; system("pause"); return 0; }
三、虚析构与纯虚析构的作用
解决父类指针或父类引用无法释放子类资源导致的内存泄露问题 。
现在可以来做个对比,首先写一个无虚析构或无纯虚析构的父类代码案例,看看最后的打印信息
-
无虚析构或无纯虚析构的父类代码案例:
#include <iostream>
using namespace std;
class Father
{
public:
// 父类的析构函数
~Father()
{
std::cout << "父类的析构函数调用" << std::endl;
}
};
class Son : public Father
{
public:
// 子类的析构函数
~Son()
{
std::cout << "子类的析构函数调用" << std::endl;
}
};
int main()
{
Father * father = new Son;
delete father;
system("pause");
return 0;
}
运行结果截图如下,可见未调用子类的析构函数,如果此时的子类申请了堆区资源,必将造成内存泄露问题:
-
有虚析构或纯虚析构的父类代码案例
#include <iostream>
class Father
{
public:
// 父类的构造函数
Father()
{
std::cout << "父类的构造函数调用" << std::endl;
}
// 父类的虚析构函数
virtual ~Father()
{
std::cout << "父类的虚析构函数调用" << std::endl;
}
};
class Son : public Father
{
public:
// 子类的构造函数
Son(std::string name)
{
std::cout << "子类构造函数的调用" << std::endl;
// 子类申请堆区资源
m_Name = new std::string(name);
}
~Son()
{
std::cout << "子类析构函数的调用" << std::endl;
// 析构子类的堆区资源
if (m_Name != NULL)
{
delete m_Name;
m_Name = NULL;
}
}
// 此处为字符串指针变量,是为了申请堆区资源做准备
std::string * m_Name;
};
int main()
{
Father * father = new Son("Jack");
delete father;
system("pause");
return 0;
}
运行结果截图如下,在此段代码中,父类使用了虚析构函数,子类申请了堆区资源,在释放父类资源的同时也释放了子类资源。
如果喜欢此文章,可以点赞哦,我会无偿分享所有源码与自己的C++成长之路 !