为什么需要智能指针?

news2025/1/16 6:02:36

为什么需要智能指针?

  1. 解决忘记释放内存导致内存泄漏的问题。
  2. 解决异常安全问题。
#include<iostream>
using namespace std;

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");
	return a / b;
}
void Func()
{
	// 1、如果p1这里new 抛异常会如何?
	// 2、如果p2这里new 抛异常会如何?
	// 3、如果div调用这里又会抛异常会如何?
	int* p1 = new int;
	int* p2 = new int;
	cout << div() << endl;
	delete p1;
	delete p2;
}
int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

问:如果p1这里new 抛异常会如何?

答:p1、p2不会开空间,内存没有释放

问:如果p2这里new 抛异常会如何?

答:p2不会开空间,内存没有得到释放

问:如果div调用这里又会抛异常会如何?

答:内存没有被释放。

那么如何解决呢?

可以利用智能指针来解决这个问题。

智能指针的使用及其原理

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显示地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

我们用一个类来封装一下这个指针,实现如下:

#include<iostream>
using namespace std;

namespace hayaizo
{
	template<class T>
	class smart_ptr
	{
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		~smart_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}
	private:
		T* _ptr;
	};
}

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

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

	return 0;
}

运行结果:

既然是指针,那么也需要支持解引用*->,重载这两个符号就好了。

namespace hayaizo
{
	template<class T>
	class smart_ptr
	{
	public:
		smart_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		~smart_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

但这样地封装会有一个致命的缺点,一个地址只能被一个智能指针指向,不然会导致同一块内存释放两次的问题,我们看看官方库中的auto_ptr是怎么解决的。

可以看到,sp1的地址变成了sp2的地址了,然后sp1的地址变成了nullptr

手搓一个低配版的auto_ptr

namespace hayaizo
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>&ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				if (_ptr)
				{
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

但这样确实可以解决问题,但是已经失去了原生指针的功能了,原生指针是支持同一个地址被很多个指针指向的,在介绍解决方法之前得先介绍unique_ptr

unique_ptr

unique_ptr非常粗暴,直接不让你拷贝,把拷贝构造禁掉了。

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

		unique_ptr(unique_ptr<T>& ap)=delete
		{}

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共 享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源。
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。

比如我用三个智能指针指向地址0x00112233,因此,计数为3,当计数等于0的时候再进行销毁。

那么,这里的计数可以单纯用一个int _cnt或者static int _cnt来表示吗?

答案是否定的。

如果是int _cnt,那么每个对象都是单独的计数。

如果是``static int _cnt`,那么每个对象都是用的同一份计数。

所以这里需要用一个指针来表示计数。

template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{}

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{}

		void Release()
		{
			if (--(*_cnt) == 0)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
			return *_cnt;
		}

		T* get() const
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

		~shared_ptr()
		{
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

}

循环引用

这里的n1n2的引用计数都是2,所以形成了相互制约的局面。

n1的销毁看n2n2的销毁看n1

weak_ptr

weak_ptr是为配合shared_ptr而引入的一种智能指针。

weak_ptr可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起shared_ptr引用记数的增加或减少

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		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;
	};

struct Node
{
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

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


int main(void)
{
	hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;


	return 0;
}

很简单,就是n1内部的指针不参与引用计数,用另外的类封装起来就好了,就不会动shared_ptr<T>里面的引用计数了。

定制删除器

其实就是个仿函数,可以自己传删除方案。

//默认删除器
	template<class T>
	struct Delete
	{
		void operator()(T* ptr)
		{
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{}

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{}

		void Release()
		{
			if (--(*_cnt) == 0)
			{
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
			return *_cnt;
		}

		T* get() const
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

		~shared_ptr()
		{
			Release();
		}

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

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 

总代码:

//#include<iostream>
//using namespace std;
//
//int div()
//{
//	int a, b;
//	cin >> a >> b;
//	if (b == 0)
//		throw invalid_argument("除0错误");
//	return a / b;
//}
//void Func()
//{
//	// 1、如果p1这里new 抛异常会如何?
//	// 2、如果p2这里new 抛异常会如何?
//	// 3、如果div调用这里又会抛异常会如何?
//	int* p1 = new int;
//	int* p2 = new int;
//	cout << div() << endl;
//	delete p1;
//	delete p2;
//}
//int main()
//{
//	try
//	{
//		Func();
//	}
//	catch (exception& e)
//	{
//		cout << e.what() << endl;
//	}
//	return 0;
//}

#include<iostream>
using namespace std;

namespace hayaizo
{
	template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		auto_ptr(auto_ptr<T>&ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		auto_ptr<T> operator=(auto_ptr<T>& ap)
		{
			if (this != &ap)
			{
				if (_ptr)
				{
					delete _ptr;
				}

				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

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

		unique_ptr(unique_ptr<T>& ap)=delete
		{}

		unique_ptr<T> operator=(unique_ptr<T>& ap)=delete
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
			}
		}

		T& operator*()
		{
			return *_ptr;
		}

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

	//默认删除器
	template<class T>
	struct Delete
	{
		void operator()(T* ptr)
		{
			cout << "delete: " << ptr << endl;
			delete ptr;
		}
	};

	template<class T,class D=Delete<T>>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_cnt(new int(1))
		{}

		shared_ptr(shared_ptr<T>& ap)
			:_ptr(ap._ptr)
			,_cnt(ap._cnt)
		{}

		void Release()
		{
			if (--(*_cnt) == 0)
			{
				cout << "delete: " << _ptr << endl;
				D del;
				del(_ptr);
				//D()(_ptr);匿名对象调用()
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//p1=p1的情况
			if (_ptr == sp._ptr)
			{
				return *this;
			}

			Release();
			
			_ptr = sp._ptr;
			_cnt = sp._cnt;
			(*_cnt)++;
			return *this;
		}

		int use_count()
		{
			return *_cnt;
		}

		T* get() const
		{
			return _ptr;
		}

		T& operator*()
		{
			return *_ptr;
		}

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

		~shared_ptr()
		{
			Release();
		}

	private:
		T* _ptr;
		int* _cnt;
	};

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		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;
	};

}

struct Node
{
	int _val;

	hayaizo::weak_ptr<Node> _next;
	hayaizo::weak_ptr<Node> _prev;

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

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

template<class T>
struct Free
{
	void operator()(T* ptr)
	{
		cout << "free" << ptr << endl;
		free(ptr);
	}
}; 


int main(void)
{
	/*hayaizo::shared_ptr<Node> n1(new Node);
	hayaizo::shared_ptr<Node> n2(new Node);
	
	n1->_next = n2;
	n2->_prev = n1;*/
	hayaizo::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);
	hayaizo::shared_ptr<Node> n2(new Node);
	hayaizo::shared_ptr<int, DeleteArray<int>> n3(new int[5]);
	hayaizo::shared_ptr<int, Free<int>> n4((int*)malloc(sizeof(int)));


	return 0;
}

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

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

相关文章

入门指南 | 如何系统搭建自己的营销战略学习体系成为领域专家?

独自进入一个行业&#xff0c;如果你没有几年的行业经验或者独特的营销方式&#xff0c;很难在行业里站住脚&#xff08;每个行业潜规则都很多&#xff09;。 每个行业都有周期&#xff0c;都有很多竞争对手&#xff0c;你扎进去一个具体的行业&#xff0c;对于各种资源有限的自…

Mysql 和Oracle的区别

、mysql与oracle都是关系型数据库&#xff0c;Oracle是大型数据库&#xff0c;而MySQL是中小型数据库。但是MySQL是开源的&#xff0c;但是Oracle是收费的&#xff0c;而且比较贵。 1 2 mysql默认端口&#xff1a;3306&#xff0c;默认用户&#xff1a;root oracle默认端口&…

FileZilla Server安装配置使用说明

作者&#xff1a;John 链接&#xff1a;https://www.zhihu.com/question/20577011/answer/2360828234 来源&#xff1a;知乎 第一步&#xff1a;右键点击”立即下载“ 第二步&#xff1a;服务器端点击&#xff0c;“windows平台”版本 第三步&#xff1a;这个安装最新的“Fi…

时序预测 | Matlab实现基于GRNN广义回归神经网络的电力负荷预测模型

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 时序预测 | Matlab实现基于GRNN广义回归神经网络的电力负荷预测模型 1.Matlab实现基于GRNN广义回归神经网络的电力负荷预测模型 2.单变量时间序列预测; 3.多指标评价,评价指标包括:R2、MAE、MBE等,代码质量极高…

由浅入深学习Tapable

文章目录 由浅入深学习TapableTapable是什么Tapable的Hook分类同步和异步的 使用Sync*同步类型钩子基本使用bailLoopWaterfall Async*异步类型钩子ParallelSeries 由浅入深学习Tapable webpack有两个非常重要的类&#xff1a;Compiler和Compilation。他们通过注入插件的方式&a…

机器学习笔记 - 基于PyTorch + 类似ResNet的单目标检测

一、获取并了解数据 我们将处理年龄相关性黄斑变性 (AMD) 患者的眼部图像。 数据集下载地址,从下面的地址中,找到iChallenge-AMD,然后下载。 Baidu Research Open-Access Dataset - DownloadDownload Baidu Research Open-Access Datasethttps://ai.baidu.com/bro…

2000-2021年全国31省经济高质量发展综合指数(熵值法)(含stata do文档计算代码)

2000-2021年全国31省经济高质量发展综合指数熵值法(包括数据和计算代码) 1、时间&#xff1a;2000-2021年 3、范围&#xff1a;31省 4、来源&#xff1a;整理自中经网和统计NJ 5、指标&#xff1a;GDP增长率、研发投入强度、投资效率、技术交易活跃度、需求结构、城乡结构、…

vue3+ts+vite手把手教你创建Vue3项目

概述 简介&#xff1a; 图文详解&#xff0c;带你使用 Vite 从零到一搭建 Vue3 项目&#xff0c;快速完成项目基建 技术栈&#xff1a; Vue3 TypeScirpt Vite Element-plus 内容&#xff1a; husky代码提交校验、router安装及模块化路由、动态路由siadebar封装、less/scss使…

GrapeCity Documents for Excel, .NET Crack

GrapeCity Documents for Excel, .NET 增加了对双面打印的支持。 GcExcel.NET支持PrintOutOptions类中的Duplex枚举&#xff0c;以启用/禁用页面上的双面打印。 枚举中有四个选项&#xff0c;用户可以相应地使用它们来打印工作簿&#xff1a; 双面打印。Default表示打印机的默认…

ElasticSearch安装与启动

ElasticSearch安装与启动 【服务端安装】 1.1、下载ES压缩包 目前ElasticSearch最新的版本是7.6.2&#xff08;截止2020.4.1&#xff09;&#xff0c;我们选择6.8.1版本&#xff0c;建议使用JDK1.8及以上。 ElasticSearch分为Linux和Window版本&#xff0c;基于我们主要学习…

EMQX物联网竟然用这个?(一)——简介

一、前言 我们这些年&#xff0c;“物联网”这个名称越来越被大家所知道了。 物联网 &#xff08;Internet of things&#xff09;&#xff0c;简称 IoT&#xff0c;这个概念在1991年就被漂亮国提出来了&#xff0c;解释一下就是万物可以通过互联网连接起来&#xff0c;可以进…

uniapp 官方扩展组件 uni-combox 实现:只能选择不能手写(输入中支持过滤显示下拉列表)

uniapp 官方扩展组件 uni-combox 实现&#xff1a;只能选择不能手写&#xff08;输入中支持过滤显示下拉列表&#xff09; uni-comboxuni-combox 原本支持&#xff1a;问题&#xff1a; 改造源码参考资料 uni-combox uni-combox 原本支持&#xff1a; 下拉选择。输入关键字&am…

MySQL 数据库文件的导入导出

目录 数据库的导出 导出整个数据库 导出数据库中的数据表 导出数据库结构 导出数据库中表的表结构 导出多个数据库 导出所有数据库 数据库的导入 数据库的导出 mysqldump -h IP地址 -P 端口 -u 用户名 -p 数据库名 > 导出的文件名 用管理员权限打开cmd进入MySQL的bi…

Java StringBuffer和StringBuilder类

由于String的不可更改特性&#xff0c;为了方便字符串的修改&#xff0c;Java中又提供StringBuilder和StringBuffer类。与String不同的是&#xff0c;StringBuffer和StringBuilder是对字符串本身进行修改&#xff0c;并且不产生新的对象&#xff0c;而String是产生新的字符串进…

中科亿海微浮点数转换定点数

引言 浮点数转换定点数是一种常见的数值转换技术&#xff0c;用于将浮点数表示转换为定点数表示。浮点数表示采用指数和尾数的形式&#xff0c;可以表示较大范围的数值&#xff0c;但存在精度有限的问题。而定点数表示则采用固定小数点位置的形式&#xff0c;具有固定的精度和范…

win10安装mysql和c++读取调用举例

一、下载mysql8.rar解压到C盘(也可以解压到其他位置) 在系统环境变量添加JAVA_HOMEC:\myslq8&#xff0c;并在path中添加%JAVA_HOME%\bin; 二、以管理员身份进入命令窗口 三、修改配置文件指定安装路径和数据库的存放路径 四、键入如下命令初始化并启动mysql服务,然后修改登录…

Linux下grep通配容易混淆的地方

先上一张图: 我希望找到某个版本为8的一个libXXX.8XXX.so ,那么应该怎么写呢? 先看这种写法对不对: 是不是结果出乎你的意料之外? 那么我们来看一下规则: 这里的 "*" 表示匹配前一个字符的零个或多个 于是我们就不难理解了: lib*8*.so 表示 包…

一篇文章搞懂如何使用JDBC操作数据库,还有JDBC进阶操作

目录 简介什么是JDBC如何使用JDBC1、获取连接2、操作数据3、关闭连接&#xff0c;释放资源使用技巧 查询操作创建表&#xff0c;插入模拟数据使用Java查询数据的数据SQL注入问题使用PreparedStatement查询 更新操作插入插入并获取主键更新删除 JDBC事务JDBC的批量操作JDBC连接池…

微服务与Nacos概述-5

引入OpenFeign 添加依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency><groupId>com.alibaba.cloud</groupId>…

【Linux】CentOS7.3环境下安装Mysql 8.0.30

CentOS7.3环境下安装Mysql 8.0.30 1.mysql官网下载安装包 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 2.上传安装包到linux服务器并解压 tar -vxf mysql-8.0.30-linux-glibc2.12-x86_64.tar.xz修改名称 mv mysql-8.0.30-linux-glibc2.12-x86_6…