C++11续——智能指针(出现原因至源码模拟)

news2024/11/20 20:40:14

前言:在C++11里面提出了一个新的语法 try catch用来捕捉异常,这样子能不使用return和exit的前提下退出程序就得到错误信息,但是随之而来的就是一个新的问题,try  catch退出程序之后可能带来了无法释放的内存泄露问题,原因是try catch是跳跃式捕捉的。

目录

一,try  catch带来的内存泄漏问题

二,智能指针

1,auto_ptr

1)简单使用

2)源码模拟

3)auto_ptr的缺陷

2,unique_ptr

3,shared_ptr

1)引言

2)源码模拟

 3)shared_ptr的缺陷

4,weak_ptr


一,try  catch带来的内存泄漏问题

大家先看一段代码

class A {
public:
	A() {
		d = new int(666);
	}
	~A() {
		cout << "delete d" << endl;
		delete d;
	}
private:
	int* d;
};
void test02() {
	int* a = new int(10);
	int* b = new int(0);
	A d;
	if (*b == 0)
		throw "除零错误";
	int c = *a / *b;
	cout << "delete a"<<endl;
	delete a;
	cout << "delete b" << endl;
	delete b;
}
int main() {
	try {
		test02();
	}
	catch(const char* s){
		cout << s << endl;
	}
	return 0;
}

大家有没有想到这段简单的代码有致命的错误,没错就是内存泄漏,但是里面有类开辟的靠近,也有函数自己手动开辟的空间,到底哪些空间没有被释放呢?

 答案是类会被调用析构函数释放空间,而函数自己开辟的空间无法被释放,因为代码全部被跳过不执行了,这样子在我们编写大型程序时,如果不加以约束和处理,内存泄漏将会是巨大的问题,那么我们有没有解决办法呢?

二,智能指针

答案是有解决办法,我们发现虽然函数的代码段被跳过了,但是创建的类的析构函数还是会被调用,那么我们如果利用一个类来帮助我们自动管理这些开辟的空间不就可以避免内存泄漏了吗?有了思路现在我们开始本文正片——智能指针。

1,auto_ptr
1)简单使用

这个auto_ptr是C++98提出来的,但是它有一个比较致命缺陷,C++11官方也提出了解决办法,我们这里先不讲他的缺陷,大家先看它的用法及原理,后面我会引导大家理解它的缺陷。

	//简单使用auto_ptr
    auto_ptr<int> a ( new int(10));
	cout << *a;

 除此之外auto_ptr还支持,一些运算符重载

大家想要仔细研究可以打开

auto_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/auto_ptr/?kw=auto_ptr

2)源码模拟

如果让我们写一个auto_ptr,我们该如何下手呢?首先auto_ptr本质就是一个类容器,我们利用模板,就能实现识别指针应该是什么类型,然后我们在里面重新定义一个指针,指向传来开辟的空间不就行了吗?

从这个思路出发,我们先写出类的基本框架

namespace bit {
	template<class P>
	class auto_ptr {
	public:
		auto_ptr(P* p) {
			this->p = p;
		}
		~auto_ptr() {
			delete p;
			p = nullptr;
		}
	private:
		P* p;
	};
};

上面的代码不就实现了一个类自动管理指针开辟的空间了吗?

至于里面的一些函数功能,相信学到智能指针这块的我们早已经轻车熟路了,如果实在不懂可以参考我往期博客STL源码刨析。

namespace bit {
	//auto_ptr没有解决复制拷贝的问题
	//当拷贝的时候,被拷贝的auto_ptr的管理权就丧失了,其管理的资源置为了空,
	//并且auto_ptr无法管理数组
	template<class P>
	class auto_ptr {
	public:
		auto_ptr(P* p) {
			this->p = p;
		}
		auto_ptr(bit::auto_ptr<P>& a) {
			p = a.p;
			a.get() = nullptr;
		}
		P* get() {
			return p;
		}
		P* operator->() {
			return p;
		}
		P& operator*() {
			return *p;
		}
		auto_ptr& operator=(auto_ptr a) {
			p = a.get();
			a.get() = nullptr;
			return this;
		}
		~auto_ptr() {
			delete p;
			p = nullptr;
		}
	private:
		P* p;
	};
};
3)auto_ptr的缺陷

 auto_ptr有什么缺陷呢?答案是=重载和拷贝构造,大家先看我运行一段代码及运行结果

这是为什么呢?因为auto_ptr不支持多个智能指针指向同一块空间,因此当我们访问被赋值或者被拷贝构造的原指针就会出现报错,因为赋值或者拷贝构造完成后原指针指向空间会被变成nullptr,这就带来了一个问题,如果我们在接下来的代码里面一旦不小心访问到了原指针就会导致程序报错崩溃。

很多人就想说了,我直接多个指针指向同一块空间不就行了?但是又因此衍生出来了一个问题,那就是析构函数的时候,空间只能释放一次,但是这么多auto_ptr指向这块空间该由谁来析构呢?大家不用担心,我会在shared_ptr讲解决方案的。

2,unique_ptr

unique_ptr作为auto_ptr的一个优化版本,它的解决办法堪称简单粗暴,既然你的拷贝构造和赋值有问题,那我直接把它们定义成私有(相当于delete,外部无法调用,自然相当于被禁止了)不准你们使用不就行了吗?使用方法和auto_ptr差不多,我们就直接看源码模拟吧

namespace bite
{
	template<class T>
	class unique_ptr
	{
		// RAII
	public:
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
				delete _ptr;
		}

		// 具有指针类似行为
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		// 防止被拷贝--禁止调用拷贝构造&赋值运算符重载
#if 0
		// C++98:只声明不定义 & private
	private:
		unique_ptr(const unique_ptr<T>& up);
		unique_ptr<T>& operator=(const unique_ptr<T>& up);
#endif

		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up)=delete;
	protected:
		T* _ptr;
	};
}
3,shared_ptr
1)引言

unique_ptr终归有点简单粗暴了,我们还是有一些多个指针指向同一块空间的使用场景,那我们该如何处理这一块空间呢?

这就不得不涉及到一个巧妙的解决方案了,所有指向同一块空间的智能指针里面都存储一个指针,这个指针里面存储指向这块空间的人数,当人数减为0的时候析构函数才真正的释放资源,否则就减减,这样子就能保证空间一定是最后一个使用的人释放,不会出现同一块空间被多次释放的问题了。

那么话不多说,我们直接开始源码模拟吧。

2)源码模拟

首先我们讲解赋值拷贝

我们只需要将count++即可

	template<class T>
	class shared_ptr {
	public:	
        shared_ptr(shared_ptr<T>& s) {
			data = s.data;
			count = s.count;
			(*count)++;
		}
    private:
		int* count;
		T* data;
	};

=号重载有一个小坑,那就是原本的shared_ptr已经指向一块空间了,我们不能想赋值构造那样子无脑的赋值,我们需要先把原本指向的空间进行count--,如果count--之后等于0,那么我们就必须将空间释放再赋值。

shared_ptr& operator=(const shared_ptr<T>& s) {
			if (data != s.data) {
				if (-- * count == 0) {
					delete data;
				}
				(*s.count)++;
				data = s.get();
				count = s.count;
				
			}
				return *this;
		}

 其他的也就一个析构函数再谈一下吧,析构函数需要检查count,判断释放需要释放资源。

~shared_ptr() {
			if (--*count == 0) {
				delete data;
				delete count;
				cout << "delete" << endl;
			}
		}

完整源码

	template<class T>
	class shared_ptr {
	public:
		shared_ptr(shared_ptr<T>& s) {
			data = s.data;
			count = s.count;
			(*count)++;
		}
		shared_ptr(T* s=nullptr) {
			data = s;
			count = new int{ 1 };
		}
		T& operator*() {
			return *data;
		}
		T* operator->() {
			return data;
		}
		shared_ptr& operator=(const shared_ptr<T>& s) {
			if (data != s.data) {
				if (-- * count == 0) {
					delete data;
					cout << "delete" << endl;
				}
				(*s.count)++;
				data = s.get();
				count = s.count;
				
			}
				return *this;
		}
		T* get() {
			return data;
		}
		~shared_ptr() {
			if (--*count == 0) {
				delete data;
				delete count;
				cout << "delete" << endl;
			}
		}
	private:
		int* count;
		T* data;
	};
 3)shared_ptr的缺陷

大家看上面的shared_ptr是不是很好用,代码应该也没有错误,答案是否,大家先看一段shared_ptr经典内存泄漏代码。

	struct list {
		shared_ptr<list> prv;
		shared_ptr<list> next;
	};
	shared_ptr<list> head(new list), l1(new list);
	head->next = l1;
	l1->prv = head;

为什么说这段代码会导致内存泄漏呢? 

首先我们知道类的析构函数调用顺序,首先是调用本身的析构函数,然后调用类里面的成员类的析构函数,这样子导致了一个问题,当head调用析构函数的时候,它的count为2,减减之后为1,无法正常析构,为什么无法正常析构呢?因为head和l1d空间都是new出来的,是无法主动调用类里面的子类的析构函数将count变为0释放空间,导致最后两个count都是1,空间任然没有被正常释放,那有什么解决办法吗?且看下文讲解

4,weak_ptr

上面说了shared_ptr由于循坏引用导致死循坏,这个时候weak_ptr就应运而生了,weak_ptr也是一种智能指针,而且和shared_ptr能够相互配合使用(限用于循坏引用等情况),它作为shared_ptr的附庸存在,不能单独使用,可能会导致空间资源被多次释放。

这是怎么实现解决循坏引用的问题呢?答案是很简单,虽然shared_ptr能和weak_ptr配合使用,但是weak_ptr和shared_ptr指向同一块空间,weak_ptr并不会引起count的大小,这样子就完美解决了循坏引用的问题。

	struct list {
		weak_ptr<list> prv;
		weak_ptr<list> next;
	};
	shared_ptr<list> head(new list), l1(new list);
	head->next = l1;
	l1->prv = head;

源码模拟并不困难,在shared_pt的构造函数添加一个weak_ptr的构造函数,在weak_ptr的构造函数里面加上一个shared_ptr的构造函数就行了。

shared_ptr::shared_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/shared_ptr/shared_ptr/ 

weak_ptr::weak_ptr - C++ Referenceicon-default.png?t=N7T8https://legacy.cplusplus.com/reference/memory/weak_ptr/weak_ptr/

这里便不再进行源码模拟,留给大家练手吧。如果大家有所收获希望点赞加收藏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1675049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

资料同化 | 搭建docker环境-1

Community Gridpoint Statistical Interpolation (GSI) system DTC 是一个分布式设施&#xff0c;NWP 社区可以在这里测试和评估用于研究和操作的新模型和技术。 DTC的目标包括&#xff1a; 链接研究和操作社区 研究成果转化为实际操作的速度 加快改善天气预报 开发和测试有…

NSSCTF | [SWPUCTF 2021 新生赛]babyrce

打开题目&#xff0c;显示了一个php脚本 我们来分析一下这个脚本是什么意思 <?php error_reporting(0); header("Content-Type:text/html;charsetutf-8"); highlight_file(__FILE__); if($_COOKIE[admin]1) {include "../next.php"; } elseecho &quo…

深入解析RedisJSON:在Redis中直接处理JSON数据

码到三十五 &#xff1a; 个人主页 JSON已经成为现代应用程序之间数据传输的通用格式。然而&#xff0c;传统的关系型数据库在处理JSON数据时可能会遇到性能瓶颈。为了解决这一问题&#xff0c;Redis推出了RedisJSON模块&#xff0c;它允许开发者在Redis数据库中直接存储、查询…

Ceph集群扩容及数据再均衡原理分析

用户文件在Ceph RADOS中存储、定位过程大概包括&#xff1a;用户文件切割成对象、对象映射到PG、PG分组PGP、PG映射到OSD。这些过程中&#xff0c;可能涉及了大量概念和变量&#xff0c;而其实它们大部分是通过HASH、CRUSH等算法计算出来的&#xff0c;初始参数可能也就只有这么…

号卡极团分销管理系统 ue_serve.php 任意文件上传漏洞复现

0x01 产品简介 号卡极团分销管理系统,同步对接多平台,同步订单信息,支持敢探号一键上架,首页多套UI+商品下单页多套模板,订单查询支持实时物流信息、支持代理商自定义域名、泛域名绑定,内置敢探号、172平台、号氪云平台第三方接口以及号卡网同系统对接! 0x02 漏洞概述…

caj文件是什么?caj是什么文件?考研学生赶紧收藏!

在学术研究的广阔领域中&#xff0c;尤其是对于那些致力于深入研究、不断拓宽知识边界的考研学子们来说&#xff0c;了解并掌握各种学术资源的获取与利用方法显得尤为重要。其中&#xff0c;CAJ文件作为一种常见的学术文件格式&#xff0c;其重要性和使用频率不容忽视。那么&am…

在Linux上安装并启动Redis

目录 安装gcc环境 上传redis文件 启动redis-server 后台启动redis-server 查看redis启动状态 参考文章&#xff1a;Linux 安装 Redis 及踩坑 - 敲代码的阿磊 - 博客园 (cnblogs.com) 准备&#xff1a;打开VMware Workstation&#xff0c;创建一个虚拟机&#xff0c;进入管…

FinnConverter格式转换工具

FinnConverter简介 1. 简洁的操作界面 2. 支持多种格式相互转换 支持word转pdf&#xff1b;ppt转pdf&#xff1b;raw格式转png/jpng…&#xff1b;其他格式相互转换 2.1 输入格式支持 bmp、cr2、cr3、crw、cur、dcr、dng、doc、docx、gif、ico、jpeg、jpg、kdc、mos、nef、…

具身智能论文(四)

目录 1. Alexa Arena: A User-Centric Interactive Platform for Embodied AI2. EDGI: Equivariant Diffusion for Planning with Embodied Agents3. Efficient Policy Adaptation with Contrastive Prompt Ensemble for Embodied Agents4. Egocentric Planning for Scalable E…

ICode国际青少年编程竞赛- Python-5级训练场-综合练习5

ICode国际青少年编程竞赛- Python-5级训练场-综合练习5 1、 a 16 for i in range(6):Dev.step(1)Dev.turnLeft()Dev.step(a)Dev.step(-a)Dev.turnRight()while Dev.energy < 100:wait()Dev.step(1)a a - 5 i2、 for i in range(5):Dev.step(11 - i * 2)Dev.turnRight()wh…

1146 -Table ‘performance schema.session variables‘ doesn‘t exist的错误解决

一、问题出现 今天在本地连数据库的时候&#xff0c;发现这个问题&#xff0c;哎呦我擦&#xff0c;差点吓死了 二、解决办法 1&#xff09;找文件 用everything搜一下MySQL Server 5.7 然后去Windows服务找一下MySQL配置文件的具体路径 如果知道那最好&#xff0c;不知道那…

水雨情监测系统—实时监测水位信息

TH-SW3水雨情监测系统是一种专门用于实时监测和收集水文气象数据的自动化系统。它能够实时获取区域内降雨和水情数据&#xff0c;并将其存储到数据库中进行分析处理&#xff0c;从而为防汛指挥人员提供及时准确的信息服务。 水雨情监测系统的主要功能包括实时监测水位、流速、流…

qt cmake加入程序exe图标

可以看到qt自动编译出来的图标是默认的&#xff0c;如下图所示 我想要更改成自定义的图标&#xff0c;比如下方的样子 下边是操作步骤&#xff1a; 图标选择与转化成ico 通过这个网站将正常图片转化成ico&#xff1a;https://www.bitbug.net/创建rc文件 将ico复制到cmakelis…

Windows内核--Kernel API简析(3.1)

如果所有的内核提供的功能&#xff0c;内核提供进程/线程创建和终止&#xff0c;内存分配和释放&#xff0c;文件操作&#xff0c;网络功能&#xff0c;驱动程序加载和卸载等功能。这些API将在后面陆续介绍&#xff0c;如下先介绍Kernel提供的基础API(Kernel自身或Driver使用).…

k8s v1.20二进制部署 部署 CNI 网络组件 部署 Calico

一、部署 flannel 1.1.K8S 中 Pod 网络通信 ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器&#xff08;Pod 内的容器是不会跨宿主机的&#xff09;共享同一个网络命名空间&#xff0c;相当于它们在同一台机器上一样&#xff0c;可以用 localhost 地址访问彼此的端口。…

【Web】2023香山杯决赛 security system 题解

目录 step -1 step 0 step 1 step 2 step 3 step -1 ①题目hint&#xff1a;想办法修改属性值后进入java的原生反序列化&#xff0c;然后利用jackson链写入内存马 ②jackson反序列化基础&#xff1a; ObjectMapper objectMapper new ObjectMapper(); String jsonStrin…

Java毕业设计 基于SpringBoot vue药店管理系统

Java毕业设计 基于SpringBoot vue药店管理系统 SpringBoot 药店管理系统 功能介绍 员工 登录 个人中心 修改密码 个人信息 查看供应商信息 查看药品 查看进货 查看销售 管理员 登录 个人中心 修改密码 个人信息 供应商类型管理 供应商信用等级类型管理 药品类型管理 供应商信…

基于STM32F401RET6智能锁项目(BS82166A_3触摸按键)

一、BS81x 特征 • 工作电压&#xff1a; 2.2V~5.5V • 低待机电流 • 自动校准功能 • 可靠的触摸按键检测 • 自动切换待机 / 工作模式 • 最长按键输出时间检测 • 具备抗电压波动功能 • Level Hold &#xff0c;可选高有效或低有效 • NMOS 输出内建上…

卷积网络项目:实现识别鲜花四分类对比LeNet5、VGG16、ResNet18、ResNet34分类网络

卷积四分类项目 Gitee传送门 分类目标选取 鲜花 杏花 apricot_blossom桃花 peach_blossom梨花 pear_blossom梅花 plum_blossom 模型选择 卷积 LeNet5VGG16ResNet18ResNet34 以图搜图 获取相似度前10的搜图结果 数据清洗 鲜花四分类 删除非图片文件 删除重复图片 整理…

RS3236-3.3YUTDN4功能和参数介绍及PDF资料

RS3236-3.3YUTDN4功能和参数介绍及PDF资料-公司新闻-配芯易-深圳市亚泰盈科电子有限公司 品牌: RUNIC(润石) 封装: XDFN-4-EP(1x1) 描述: 带过温保护 输出类型: 固定 最大输入电压: 7.5V 输出电压: 3.3V 最大输出电流: 500mA RS3236-3.3YUTDN4 是一款低压差线性稳压器&#x…