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

前言:在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/

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序 19:小程序分包

对小程序进行分包的好处主要有以下两点 可以优化小程序首次启动的下载时间在多团队共同开发时可以更好的解偶协作 分包前小程序的项目构成 分包前&#xff0c;小程序项目中所有的页面资源都被打包到一起&#xff0c;导致整个项目体积过大&#xff0c;影响小程序首次启动的下…

vue自定义权限指令

定义v-hasPermi指令 /*** v-hasPermi 操作权限处理*/import useUserStore from /store/modules/userexport default {mounted(el, binding, vnode) {const { value } bindingconst all_permission "*:*:*";const permissions useUserStore().permissions&#xff…

算法-卡尔曼滤波之基本数学的概念

1.均值 定义&#xff1a;均值是一组数据中所有数值的总和除以数据的数量。均值是数据的中心趋势的一种度量&#xff0c;通常用符号 xˉ 表示。 &#xff1a;对于包含 n 个数据的数据集 {&#x1d465;1,&#x1d465;2,...,&#x1d465;&#x1d45b;}&#xff0c;均值 xˉ 计…

常见 Web 安全攻防总结

Web 安全的对于 Web 从业人员来说是一个非常重要的课题&#xff0c;所以在这里总结一下 Web 相关的安全攻防知识&#xff0c;希望以后不要再踩雷&#xff0c;也希望对看到这篇文章的同学有所帮助。今天这边文章主要的内容就是分析几种常见的攻击的类型以及防御的方法。 也许你对…

利用CAD绘制角度斜线的简易指南---模大狮模型网

在CAD设计中&#xff0c;绘制角度斜线是常见的需求&#xff0c;尤其在工程、建筑等领域中。正确绘制角度斜线不仅可以提高图纸的清晰度和美观度&#xff0c;还有助于准确表达设计意图。本文将介绍如何利用CAD软件进行角度斜线的绘制&#xff0c;为您提供简明易懂的操作指南。 一…

什么是资源池技术?它有什么用?

在开发应用程序过程中&#xff0c;涉及到对系统资源进行有效管理时往往会用到池化操作。资源池模式的应用场景很多&#xff0c;可以管理那些想要通过重用来分摊昂贵初始化代价的对象&#xff0c;而管理数据库连接就是很好的一种应用场景。数据库连接池作为一种典型的池化技术手…

云端的艺术革命:云渲染如何重塑动画与视觉特效产业

在 2019 年&#xff0c;乔恩费儒&#xff08;Jon Favreau&#xff09;决定重拍迪士尼的经典电影《狮子王》。他的创新构想是以真实动物为模型&#xff0c;在非洲草原上拍摄&#xff0c;由真实动物“出演”的辛巴和其他角色&#xff0c;随后通过配音赋予它们生命。 为了实现这一…

vue前端时间段选择控件

实现效果: 可选具体的某天的某时某分某秒 vue前端代码: <el-form-item label"日期"><el-date-pickerv-model"daterangerq"style"width: 240px"value-format"yyyy-MM-dd HH:mm:ss"type"datetimerange"range-separat…

[笔记] srlua库编译

文章目录 前言一、环境二、编译过程2.1 gcc安装2.2 编译lua2.3 编译srlua库 三、测试srlua库参考总结 前言 一、环境 centos7.9 gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) lua5.1源码 srlua 源码 二、编译过程 2.1 gcc安装 yum install gcc这里gcc安装过程和环…

基础学习-Git(分布式版本控制系统)

学习视频推荐 http://【黑马程序员Git全套教程&#xff0c;完整的git项目管理工具教程&#xff0c;一套精通git】 https://www.bilibili.com/video/BV1MU4y1Y7h5/?p5&share_sourcecopy_web&vd_source2b85bd9be9213709642d908906c3d863 1、Git环境配置 安装Git Git下…

CAXA 3D实体设计2024:塑造未来的创新引擎

在数字化时代的浪潮中&#xff0c;3D CAD实体建模设计正成为推动工业创新的核心动力。CAXA 3D实体设计2024&#xff0c;以其卓越的性能和丰富的功能&#xff0c;为企业和个人用户带来了前所未有的设计体验。 CAXA 3D实体设计2024不仅拥有直观易用的界面&#xff0c;还配备了强…

【JS】call和 apply函数的详解

JavaScript 中 call() 和 apply() 函数的详解 在JavaScript中&#xff0c;call()和apply()都是非常重要的方法&#xff0c;用于调用函数时指定函数体内的this的值&#xff0c;从而实现不同对象之间的方法共享。尽管它们的功能非常相似&#xff0c;但在实际使用中各有其优势和特…

数据结构选择题(期末)

1.给定NN的二维数组A&#xff0c;则在不改变数组的前提下&#xff0c;查找最大元素的时间复杂度是&#xff08;A&#xff09;&#xff1a; A.O(N2) B.O(NlogN) C.O(N) D.O(N2logN) 两重循环即O(N2)的时间复杂度 2.与数据元素本身的形式、内容、相对位置、个数无关的是数据的…

OpenAI 发布新款大型语言模型 GPT-4o,带大家了解最新ChatGPT动态。

OpenAI 发布新款大型语言模型 GPT-4o 昨日OpenAI 举办了一场线上活动&#xff0c;正式发布了其最新研发的 AI 模型 GPT-4o&#xff0c;并详细介绍了该模型的强大功能和未来发展规划。此次发布标志着 AI 技术的重大突破&#xff0c;为用户提供了更加便捷、高效的 AI 工具&#…

荆州科技局副局长乔梁莅临湖北点赋网络科技公司参观调研

近日&#xff0c;荆州科技局副局长乔梁&#xff0c;莅临湖北点赋网络科技公司进行参观调研。点赋科技总经理崔梦娇亲自陪同&#xff0c;向副局长介绍了公司的D咖智能饮品机器人经营状况和研发进展情况。 在参观过程中&#xff0c;副局长乔梁对点赋科技的创新能力和技术成果给予…

【计算机毕业设计】基于SSM++jsp的高校专业信息管理系统【源码+lw+部署文档+讲解】

目录 第1章 绪论 1.1 课题背景 1.2 课题意义 1.3 研究内容 第2章 开发环境与技术 2.1 MYSQL数据库 2.2 JSP技术 2.3 SSM框架 第3章 系统分析 3.1 可行性分析 3.1.1 技术可行性 3.1.2 经济可行性 3.1.3 操作可行性 3.2 系统流程 3.2.1 操作流程 3.2.2 登录流程 3.2.3 删除信息流…

FedDML:Federated Mutual Learning

这篇把DML运用到FL上 论文地址:arvix code: 作者git 贡献 我们针对三种异质性(DOM)提出了一种新颖的联邦学习范式,称为联邦相互学习(FML)。 首先,FML 处理数据和目标通过使每个客户能够训练个性化模型来实现异质性。 从OH的意义上来说,DH对服务器有害,但对客户端有…

全面了解 Swagger 导出功能的使用方式

Swagger 是一个强大的平台&#xff0c;专门用于开发、构建和记录 RESTful Web 接口。通过其提供的交互式用户界面&#xff0c;开发人员能够轻松且迅速地创建和测试 API。Swagger 还允许用户以多种格式&#xff0c;包括 JSON 和 Markdown&#xff0c;导出 API 文档。选择 JSON 格…

Go微服务: Prometheus性能监控与Grafana平台的搭建

Prometheus 概述 promethues 是一套开源的监控&报警&时间序列数据库的组合基本原理是通过http协议周期性抓取被监控组件的状态适合Docker、Kubernetes环境的监控系统 Promethues 整体架构 一、抓取数据的两种方式 1 &#xff09;Short-lived jobs 短暂的任务 不会提…

The Quantcast File System——论文泛读

VLDB 2013 Paper 分布式元数据论文阅读笔记整理 问题 在2013年之前&#xff0c;由于网络链路带宽有限&#xff0c;数据在集群中移动速度慢&#xff0c;因此Hadoop尽量将数据留在原来的位置&#xff0c;并将处理代码发送给它。随着网络链路的发展&#xff0c;可以之前更高的数…