6.4虚析构函数和纯虚析构函数

虚析构函数和纯虚析构函数

如果有一定基础的伙伴来看这篇文章之前应该都知道虚析构函数的用途,虚析构函数就是防止有有没有释放干净的内存,防止内存泄漏。

没学过也没有关系我们通过了解原理的过程来学习这个虚析构和纯虚析构函数。

首先字面意思,分开理解,先看虚析构函数,析构函数就是释放内存的东西,虚函数,第六章我们都没离开这个东西,所以我们可以和虚表联系起来。那就是有虚表的析构函数吧。(纯虚析构函数我们最后再讲)

通过画图来理解我们的整个过程。(最近发现画图是真好用,形象)

最开始我们来探究我们没有虚析构函数的代码,然后再来对比有虚函数的代码,对比了才明显

先贴上我们要的没有虚析构函数刨析的代码:

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A中构造函数的调用" << endl;
		p_A = new int(666);
	}
	~A()
	{
		if (p_A != NULL)
		{
			cout << "A中析构函数的调用" << endl;
			delete(p_A);
			p_A = NULL;
		}
	}
	int* p_A;
};
class B:public A
{
public:
	B()
	{
		cout << "B中构造函数的调用" << endl;
		p_B = new int(999);
	}
	~B()
	{
		if (p_B != NULL)
		{
			cout << "B中析构函数的调用" << endl;
			delete(p_B);
			p_B = NULL;
		}
	}
	int* p_B;
};
int main()
{
	A* a = new B;
	delete(a);
}

看看运行的结果:

在这里插入图片描述
很明显少了一次我们对B的析构函数,但是我们的B在堆区开辟了一个空间,所以我们造成了内存泄漏。

那么这个过程是怎么样的呢?

第一个过程:

在这里插入图片描述

第二个过程:

我们都知道,我们应该先释放子类,然后再释放父类吧。关于原理前面文章讲过这里不细说。

形象比喻:有了爸爸才有儿子,儿子才刚刚生下来,坑定不能爸爸就挂了吧,所以先挂儿子,再挂爸爸。

在这里插入图片描述

到这里就没有然后了,程序就结束了,很明显,我们new的p_B还没有被释放,所以造成了内存的泄漏,那么我们又来看看为什么加上了virtual又能解决这个问题?

上新代码:

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A中构造函数的调用" << endl;
		p_A = new int(666);
	}
	virtual ~A()
	{
		if (p_A != NULL)
		{
			cout << "A中析构函数的调用" << endl;
			delete(p_A);
			p_A = NULL;
		}
	}
	int* p_A;
};
class B:public A
{
public:
	B()
	{
		cout << "B中构造函数的调用" << endl;
		p_B = new int(999);
	}
	 virtual ~B()
	{
		if (p_B != NULL)
		{
			cout << "B中析构函数的调用" << endl;
			delete(p_B);
			p_B = NULL;
		}
	}
	int* p_B;
};
int main()
{
	A* a = new B;
	delete(a);
}

在这里插入图片描述

调用析构函数的时候都先查表,然后找到自己相应的析构函数然后调用。

什么是纯虚析构函数呢?

纯虚构析构函数 就是纯虚函数加上析构函数,一般我们把函数设置纯虚函数都是不想这个类实例化,抽象出来的顶层父类,并且这个纯虚函数不能实现。 但是在纯虚析构这里不同 因为析构函数的调用顺序是派生类 成员类 基类,就算你基类是纯虚函数,编译器还是会产生对他的调用,所以要保证为纯虚析构提供函数体,如果你不做有的编译器会自动加上,但是有的编译器却会出现问题。如果不提供该析构函数的实现,将使得在析构过程中,析构无法完成而导致析构异常的问题。

这里面有一个误区:有人认为,virtual f()=0这种纯虚函数语法就是没有定义体的语义。其实,这是不对的。这种语法只是表明这个函数是一个纯虚函数,因此这个类变成了抽象类,不能产生对象。我们完全可以为纯虚函数指定函数体 。通常的纯虚函数不需要函数体,是因为我们一般不会调用抽象类的这个函数,只会调用派生类的对应函数。

纯虚析构需要注意的地方就是必须要提供函数体,狭隘的理解:
我们每次用虚析构函数 都是为了释放开辟出来的内存吧,如果我们的纯虚析构函数没有实现的话那么就无法发释放基类中开辟出来的内存空间。

为了不把大家弄混淆,不在这篇文章讨论另一个题外话下一篇文章我们来讨论:
假如我们没有在每个析构函数面前都加virtual会发生什么情况,分情况讨论。
假如我们就是B类本身类型接收,再加上virtual又会发生什么?

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C++中,delete是用于释放在堆上动态分配的内存,并调用相应对象的函数的运算符。delete操作符会根据所删除对象的类型找到正确的函数并进行调用。 可以通过在基类的函数前加上virtual关键字来实现多态性,使得delete操作符能够正确调用派生类的函数。这样,在删除指向基类指针的对象时,会自动调用派生类的函数。 如果在基类的函数中不加上virtual关键字,那么delete操作符只会调用基类的函数,而不会调用派生类的函数。这可能导致派生类中的资源无法正确释放,造成内存泄漏。 因此,在设计继承关系的类时,如果有可能会通过基类指针删除派生类对象,就应该在基类的函数前加上virtual关键字,以确保delete操作符能够正确调用派生类的函数。这是一种良好的编程实践。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [C++函数和delete关系](https://blog.csdn.net/Taynpg/article/details/109245219)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [6.4函数函数](https://blog.csdn.net/qq_52563729/article/details/117041875)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值