秒懂C++之智能指针

news2024/11/14 13:30:19

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

前言

智能指针的使用及原理

RAII

RAII弊端

std::auto_ptr

std::unique_ptr

std::shared_ptr

shared_ptr弊端

std::weak_ptr

扩展(删除器)


前言

为了解决抛异常所造成的内存泄漏等问题~秒懂C++之异常-CSDN博客~我们来学习智能指针的相关用法~

智能指针的使用及原理

RAII

RAII Resource Acquisition Is Initialization )是一种 利用对象生命周期来控制程序资源 (如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源 ,接着控制对资源的访问使之在对象的生命周期内始终保持有效, 最后在 对象析构的时候释放资源 。借此,我们实际上把管理一份资源的责任托管给了一个对象。

template <class	T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	}
    T& operator*() {return *_ptr;}
    T* operator->() {return _ptr;}
private:
	T* _ptr;
};

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	SmartPtr<int> sp1(new int);
	SmartPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try {
		Func();
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

可以看到即使触发了异常最后也可以清理资源~避免内存泄漏~

RAII也可以和正常指针一样解引用与->访问其内容~

RAII弊端

智能指针的拷贝都是默认生成的,而默认生成的拷贝构造都为浅拷贝这就代表拷贝后会有两个指针指向同一处空间,最终析构两次造成报错~

其实多个指针指向同一处空间进行访问并没什么大碍,迭代器也这样。不过智能指针除了访问还有释放的职能,这才导致出现问题~

std::auto_ptr

这个稍微提一下就好了,因为它是一个及其失败的设计!

过于浮夸了,直接把人家资源给掠夺了~

std::unique_ptr

unique_ptr 的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份 UniquePtr 来了解它的原
namespace lj
{
	template <class	T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr = nullptr)
			: _ptr(ptr)
		{}
		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		T& operator*() { return *_ptr; }
		T* operator->() { return _ptr; }

		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}

就是利用关键字delete防止生成默认拷贝与默认赋值,不过个人感觉实用性不大~

std::shared_ptr

shared_ptr 的原理:是通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源
  • shared_ptr 在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
  • 对象被销毁时 ( 也就是析构函数调用 ) ,就说明自己不使用该资源了,对象的引用计数减一
  • 如果引用计数是 0 ,就说明自己是最后一个使用该资源的对象, 必须释放该资源
  • 如果不是 0 ,就说明除了自己还有其他对象在使用该份资源, 不能释放该资源 ,否则其他对象就成野指针了

shared_ptr中如何合理利用引用计数是一个难点~

普通int count计数肯定是不行的,那静态成员呢?

namespace lj
{
	template<class T>
class shared_ptr
{
public:
	// RAII
	shared_ptr(T* ptr)
		:_ptr(ptr)
	{
		_count = 1;
	}

	// sp2(sp1)
	shared_ptr(const shared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		++_count;
	}

	~shared_ptr()
	{
		if (--_count == 0)
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	}

	int use_count()
	{
		return _count;
	}

	// 像指针一样
	T& operator*()
	{
		return *_ptr;
	}

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

	static int _count;
};

template<class T>
int shared_ptr<T>::_count = 0;
}

一开始还好,但当sp3被构建出来后会把count重新初始化为1,因为静态成员是属于类中的所有对象,这样一来就打乱了原本的计数。本来是想要释放两处空间的结果只释放了sp3的,由此看来静态也不合适~

我们想要的是给每一个资源都配上一个引用计数,而非全部资源共用一个!

namespace lj
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{
		}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++*_pcount;
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;

			}
		}

		int use_count()
		{
			return *_pcount;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

		T* _pcount;
	};
	
}

最终给出的解决方案是在每一个对象中存一个指向计数的指针~

除了拷贝构造,赋值拷贝也要给上~

        // sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)//防止自身赋值,也防止与拷贝自身的赋值,即指向同一资源的不赋值
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}
		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

shared_ptr弊端

struct ListNode
{
	int _val;
	/*struct ListNode* _next;
	struct ListNode* _prev;*/

	//这里也要改为智能指针
	//n1->_next为内置类型,n2为自定义类型
	lj::shared_ptr<ListNode> _next;
	lj::shared_ptr<ListNode> _prev;

	

	ListNode(int val = 0)
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	/*ListNode* n1 = new ListNode(10); 
	ListNode* n2 = new ListNode(10);*/

	//库里的std::shared不支持隐式类型转化
	//std::shared_ptr<ListNode> n1 = new ListNode(10);


	lj::shared_ptr<ListNode> n1(new ListNode(10));
	lj::shared_ptr<ListNode> n2(new ListNode(20));

	n1->_next = n2;
	n2->_prev = n1;

	

	return 0;
}

当我们把智能指针应用到双链表时会出现循环引用的弊端~

完全就是死循环了,永远释放不了~

ps:_next作为节点成员,节点什么时候释放,它就什么时候析构~

为了解决这种特定场景下的问题,我们又引入了新的智能指针,weak_ptr

std::weak_ptr

weak指针其实也就是在shared指针的基础上放弃了对引用计数的职能~

struct ListNode
{
	int _val;

	lj::weak_ptr<ListNode> _next;
	lj::weak_ptr<ListNode> _prev;



	ListNode(int val = 0)
		:_val(val)
		//weak指针无法用普通指针构造
		/*, _next(nullptr)
		, _prev(nullptr)*/
	{}

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{


	lj::shared_ptr<ListNode> n1(new ListNode(10));
	lj::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;


	return 0;
}
namespace lj
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{
		}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++*_pcount;
		}
		// sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)//防止自身赋值,也防止与拷贝自身的赋值,即指向同一资源的不赋值
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}
		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}
		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		//方便weak指针拿到shared指针
		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
	};
	
}
namespace lj
{
	// 不支持RAII,不参与资源管理
	template<class T>
	class weak_ptr
	{
	public:
		// RAII
		// 取消了引用计数
		weak_ptr()
			:_ptr(nullptr)
		{}
		//用share指针作拷贝
		weak_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
		}
		//用share指针作赋值重载
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

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

	private:
		T* _ptr;
	};
}

我们把互相牵制的_next与_prev指针变为weak指针,使得其无法进行引用计数,这样就可以把资源成功释放~

最终在weak指针的作用下解决了循环引用的问题~

扩展(删除器)

在了解删除器之前我们需要认识一点:[ ] 是要匹配使用的,如果new用了[ ] 那么delete也要使用[ ] ,否则就会报错~

而且也不是所有被智能指针修饰的资源都是delete,例如打开文件的时候我们反而是要用close, 

所以一般是搞关于删除的仿函数。

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		cout << "delete[]" << ptr << endl;
		delete[] ptr;
	}
};

int main()
{
	std::shared_ptr<ListNode> p1(new ListNode(10));
	//C11下作出的改进优化
	//std::shared_ptr<ListNode[]> p2(new ListNode[10]);

	std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());

	return 0;

}

不过比起仿函数,我们前面所学的lambda表达式会更好用一点~

int main()
{
	std::shared_ptr<ListNode> p1(new ListNode(10));
	//C11下作出的改进优化
	//std::shared_ptr<ListNode[]> p2(new ListNode[10]);

	//std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());
	std::shared_ptr<FILE> p3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });
	std::shared_ptr<ListNode> p2(new ListNode[10], [](ListNode* ptr){ delete[] ptr; });

	return 0;

}

认识到删除器后,我们再来对自己实现的shared指针进行改进调整~

 所以我们再引入前面所学的包装器function,解决这个参数问题~

#include<functional>
namespace lj
{
	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		template<class D>
		shared_ptr(T* ptr, D del)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{
		}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++*_pcount;
		}
		// sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)//防止自身赋值,也防止与拷贝自身的赋值,即指向同一资源的不赋值
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}
		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				//delete _ptr;
				_del(_ptr);
				delete _pcount;
			}
		}
		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

		// 像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
		//方便weak指针拿到shared指针
		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};
	
}

 

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

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

相关文章

【图像匹配】基于SIFT算法的图像匹配,matlab实现

博主简介&#xff1a;matlab图像代码项目合作&#xff08;扣扣&#xff1a;3249726188&#xff09; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于基于SIFT算法的图像匹配&#xff0c;用matlab实现。 一、案例背景和算法介绍 本…

【MySQL】MySQL中JDBC编程——MySQL驱动包安装——(超详解)

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解Java中JDBC编程&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;【MySQL】MySQL索引与事务的透析——&#xff08;超详解&#xff09;-CSDN博客 &#x1f308;感兴趣的小伙伴看一看小编主页&a…

【Linux】初识信号与信号产生

目录 一、认识信号 1 .什么是信号 2 .哪些情况会产生信号 3 . 查看信号 4 . 信号处理 二、产生信号 1 .通过终端按键产生信号 2 .调用系统函数向进程发信号 3 . 由软件条件产生信号 4 . 由硬件异常产生信号 一、认识信号 1 .什么是信号 你在网上买了很多件商品&#xff0c;再…

技术上,如何复现 o1?

知乎&#xff1a;周舒畅链接&#xff1a;https://zhuanlan.zhihu.com/p/720127190 基础模型 搞 o1 首先需要一个基模&#xff0c;这个基模必须是&#xff1a; 能进行“长”生成。注意这和“长 context”不是一回事。模型生成的结果&#xff0c;经常会有自激的噪声存在&#xf…

Unity多国语言支持

Unity多国语言支持 项目在我的课程 ”淘金城堡“ 中应用 项目的地址&#xff1a;http://t.csdnimg.cn/m0hFd 一、基本概念 在Unity中加入多国语言的支持可以让我们发布的游戏或应用上线在拥有不同语言的国家或地区。 下面介绍一款Unity官方提供的插件“Localization package…

USB中的传输和事务

文章目录 一、USB中的四种事务1. **控制事务&#xff08;Control Transaction&#xff09;**2. **批量事务&#xff08;Bulk Transaction&#xff09;**3. **中断事务&#xff08;Interrupt Transaction&#xff09;**4. **等时事务&#xff08;Isochronous Transaction&#x…

C++第五十弹---类型转换全解析:从静态到动态,一网打尽

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1. C语言中的类型转换 2. 为什么C需要四种类型转换 2.1、内置类型 -> 自定义类型 2.2、自定义类型 -> 内置类型 2.3、自定义类型 -&…

Suno新上线Covers翻唱新 - 实现音频风格任意转换

历史文章 Suno AI如何解决中文多音字的问题&#xff1f;耗费500积分&#xff0c;亲测有效 &#xff0c;V4版本会不会直接支持呢&#xff1f; 上传音频&#xff0c;打造贴合您喜好的风格歌曲创作&#xff0c;这一波新玩法我打8分 Suno AI Noisee AI 做抖音冥想账号实操 音乐…

网络安全-利用 Apache Mod CGI

一、环境 蚁剑官网拉取 二、开始操作 蚁剑连接 一样终端命令不能执行 可以看到putenv已经禁用 我们开始一下&#xff0c;跳入一个新终端且可以执行命令 我们具体看一下干了什么事情 上传了一个htaccess这个文件的作用是让以后所有ant文件都以cgi去执行 三、总结 cgi文件可以…

【C++】C++的多态

目录 多态的使用 多态的概念 多态的定义和实现 虚函数 构成多态的条件 特殊情况&#xff1a;协变 析构函数的重写 怎么实现 为什么实现 override和final关键字 override final 重载/重写/隐藏的对比 纯虚函数和抽象类 纯虚函数 抽象类 多态的实现 虚函数表指针…

魔方财务安装指南

本文将详细介绍魔方财务的安装、升级和迁移过程&#xff0c;确保您能够顺利地部署和使用魔方财务系统。 服务器配置一览表 以下是魔方财务1.0.0及更高版本的最低和推荐系统要求&#xff1a; 需求名称推荐配置最低要求OSCentOS/Debian/UbuntuLinux&#xff08;不要使用window…

IP协议及相关特性

IP协议负责地址管理和路由选择。它的组成为&#xff1a; 接下来我们将对其中较重要的部分进行介绍。 4位版本&#xff1a;这里的四位版本只有两个取值 分别为IPv4和IPv6&#xff0c;这两个额分别为不同的IP协议&#xff0c;但是现在主流的还是IPv4但是近年来IPv6在中国的普及率…

2022高教社杯全国大学生数学建模竞赛C题 问题一(1) Python代码演示

目录 问题 11.1 对这些玻璃文物的表面风化与其玻璃类型、纹饰和颜色的关系进行分析数据探索 -- 单个分类变量的绘图树形图条形图扇形图雷达图Cramer’s V 相关分析统计检验列联表分析卡方检验Fisher检验绘图堆积条形图分组条形图分类模型Logistic回归随机森林import matplotlib…

中秋之际,唱响工体!玛丽亚·凯莉2024演唱会北京站璀璨上演

续写传奇华章 启幕音乐盛典 中秋之际&#xff0c;全国数万乐迷翘首以待的音乐盛典如约而至。时隔多年&#xff0c;传奇天后玛丽亚凯莉惊艳开唱工体&#xff01; 夜幕降临&#xff0c;圆月高悬&#xff0c;在不绝于耳的欢呼声中&#xff0c;玛丽亚凯莉以一袭流光溢彩的礼服优雅…

【LIO】FAST-LIO论文详解

FAST-LIO论文详解 1. 摘要2. 简介1. 相关工作A. LiDAR 里程计和地图绘制 2. 实现方法A. 基础知识1. 连续模型在这里插入图片描述 B. 激光雷达测量的预处理C. 状态估计1) 前向传播&#xff1a;2) 反向传播与运动补偿&#xff1a;3) 残差计算&#xff1a; 1. 摘要 提出了一种计算…

简单题21 - 合并两个有序链表(Java)20240917

问题描述&#xff1a; java代码&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val…

Java 技巧 如何在IDEA2024 中快速打出System.out.println();

1.基本用法 键入sout回车 回车后变成&#xff1a; 2.打印变量 快速打印变量,以打印变量名为set为例&#xff0c;set.sout回车&#xff0c; 回车后变成

简单题26 - 删除有序数组中的重复项(Java)20240917

问题描述&#xff1a; java代码&#xff1a; class Solution {public int removeDuplicates(int[] nums) {if (nums.length 0) return 0; // 处理空数组情况int i 0; // 指向新数组中的最后一个不重复元素for (int j 1; j < nums.length; j) {if (nums[j] ! nums[i]) { …

室内灯具检测系统源码分享

室内灯具检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

从kaggle竞赛零基础上手CV实战(Deepfake检测)

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 从kaggle竞赛零基础上手CV实战 从kaggle竞赛零基础上手CV实战&#xff08;Deepfake检测&#xff09; 目录 从kaggle竞赛零基础上手CV实战&#xff08;Deepfake检测&#xff09;背景介绍学习地址课程大纲课程特色…