C++——智能指针

news2024/12/23 15:55:33

目录

智能指针作用

代码

auto_ptr

特点

模拟实现

 unique_ptr

模拟实现

shared_ptr

模拟实现

 shared_ptr的线程安全

 解决方式:加锁

代码

总结

循环引用

weak_ptr就可以解决这个问题

代码

模拟实现

定制删除器


智能指针作用

更好的解决了多个异常捕获不好解决的状况

代码


#include<iostream>

using namespace std;

template<class T>
class SmartPtr
{
public:

	// 保存资源
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{ }

	//释放资源
	~SmartPtr()
	{
		delete[] _ptr;
		cout << _ptr << endl;
	}

	//像指针一样

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

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

	T& operator[](size_t pos)
	{
		return _ptr[pos];
	}
private:
	T* _ptr;
};
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw "除0错误";
	return a / b;
}



void Func()
{
	SmartPtr<int> sp1(new int[10]);
	SmartPtr<int> sp2(new int[10]);

	*sp1 = 10;
	sp1[0]--;

	cout << *sp1 << endl;

	cout << div() << endl;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* s)
	{
		cout << s << endl;
	}
	return 0;
}

这种方式又叫RAII

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

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

auto_ptr

特点

拷贝构造的特点是资源转移——很多公司禁止auto_ptr。

auto_ptr<int> ap1(new int);
auto_ptr<int> ap2(ap1);

比如以上代码,他会把ap1给清空然后把资源转移给ap2 见图,会导致对象悬空。

 

模拟实现

// auto_ptr


	template<class T>
	class auto_ptr
	{
	public:

		// 保存资源
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{ }

		//释放资源
		~auto_ptr()
		{
			delete _ptr;
			cout << _ptr << endl;
		}

		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr; // ap._ptr被置空了
		}

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

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

 unique_ptr

unique_ptr的实现原理:简单粗暴的防拷贝,直接把拷贝构造给封掉!

C++可以使用在拷贝构造后边加上=delete;

模拟实现


// unique_ptr

	template<class T>
	class unique_ptr
	{
	public:

		// 保存资源
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{ }

		//释放资源
		~unique_ptr()
		{
			delete _ptr;
			cout << _ptr << endl;
		}


		// 给拷贝构造封了
		unique_ptr(unique_ptr<T>& up) = delete;



		unique_ptr& operator=(unique_ptr<T>& up)
		{
			if (this != &ap)
			{
				delete _ptr;
				_ptr = up._ptr;
				up._ptr = nullptr;
			}
			return *this;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
	};

shared_ptr

 引用计数来解决拷贝构造问题

模拟实现

// shared_ptr

	template<class T>
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
		{ }

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		void Release()// 释放资源
		{
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				(*_pcount)++;// 引用计数++
			}
		}


		shared_ptr& operator=(shared_ptr<T>& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

 shared_ptr的线程安全

// shared_ptr

	template<class T>
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
		{ }

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}
		void Release()// 释放资源
		{
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				//cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				(*_pcount)++;// 引用计数++
			}
		}


		shared_ptr& operator=(shared_ptr<T>& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
	};

	void test_shared_ptr()
	{
		int n = 10000;
		shared_ptr<int> sp1(new int(1));

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<int> sp2(sp1);
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<int> sp3(sp1);
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
	}

 结果应该为1:因为虽然在不停的拷贝构造,但是拷贝的时候引用计数会+1,析构的时候就会-1;最终应该还为1,可结果并非我们想的那样。

见图:
可能会崩溃或者引用计数不为1
这都是线程不安全的体现

 解决方式:加锁

代码


	// shared_ptr

	template<class T>
	class shared_ptr
	{
	public:

		// 保存资源
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))// 为引用计数初始化为1
			,_pmtx(new mutex)
		{}

		//释放资源
		~shared_ptr()
		{
			Release();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			,_pmtx(sp._pmtx)
		{
			_pmtx->lock(); // t1 t2
			++(*_pcount);
			_pmtx->unlock();
		}
		void Release()// 释放资源
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)// 如果当前对象的引用计数为1,则直接释放当前对象
			{
				//cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
				flag = true;
				//delete _pmtx;
			}

			_pmtx->unlock();

			// 如果flag==true说明引用计数减到0了
			if (flag == true)
			{
				delete _pmtx;
			}
		}

		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr) // 如果管理的是同一块资源的两个对象则不需要赋值
			{
				Release();

				_ptr = sp._ptr; // 资源地址赋值
				_pcount = sp._pcount; // 引用计数赋值
				_pmtx->lock();
				(*_pcount)++;// 引用计数++
				_pmtx->unlock();

			}
		}


		shared_ptr& operator=(shared_ptr<T>& sp)
		{
			if (this != &sp)
			{
				delete _ptr;
				_ptr = sp._ptr;
				sp._ptr = nullptr;
			}
			return *this;
		}

		int use_count()
		{
			return *_pcount;
		}

		//像指针一样

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

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

		T& operator[](size_t pos)
		{
			return _ptr[pos];
		}
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;
	};

	void test_shared_ptr()
	{
		int n = 10000;
		shared_ptr<int> sp1(new int(1));

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<int> sp2(sp1);
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<int> sp3(sp1);
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;
	}


比如我们把类型改为日期类Date:

 


struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};
	
	void test_shared_ptr()
	{
		int n = 100000;
		shared_ptr<Date> sp1(new Date);

		thread t1([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<Date> sp2(sp1);
					sp2->_year++;
					sp2->_month++;
					sp2->_day++;
				}
			});

		thread t2([&]()
			{
				for (int i = 0; i < n; i++)
				{
					shared_ptr<Date> sp3(sp1);
					sp3->_year++;
					sp3->_month++;
					sp3->_day++;
				}
			});

		t1.join();
		t2.join();

		cout << sp1.use_count() << endl;


		cout << sp1->_year << endl;
		cout << sp1->_month << endl;
		cout << sp1->_day << endl;
	}

总结

管理的资源不是线程安全的,但是引用计数是线程安全的。

循环引用

关于之前的循环引用的写法是:


struct ListNode
	{
		int val;
		ListNode* _next;
		ListNode* _prev;
	};

	void test_shared_ptr2()
	{
		ListNode* n1 = new ListNode;
		ListNode* n2 = new ListNode;

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

		delete n1;
		delete n2;
	}

如果我们想用智能指针进行进行修改:


	struct ListNode
	{
		int val;
		shared_ptr<ListNode> _next;
		shared_ptr<ListNode> _prev;

		//检测结点是否释放
		~ListNode()
		{
			cout << "delete" << endl;
		}
	};

	void test_shared_ptr2()
	{
		shared_ptr<ListNode> n1 = new(ListNode);
		shared_ptr<ListNode> n2 = new(ListNode);

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

		
	}

会有循环引用的问题,将造成内存泄漏


当n1和n2造成循环引用的条件的时候


当资源对应的引用计数减为0时对应的资源才会被释放,因此资源1的释放取决于资源2当中的prev成员,而资源2的释放取决于资源1当中的next成员。
而资源1当中的next成员的释放又取决于资源1,资源2当中的prev成员的释放又取决于资源2,于是这就变成了一个死循环,最终导致资源无法释放。

weak_ptr就可以解决这个问题

//可以指向/访问资源,不参与资源管理,不增加引用计数

weak_ptr只是shared_ptr的小弟,它根本不支持RAII,他只是辅助shared_ptr

代码


struct ListNode
	{
		int val;


		//可以指向/访问资源,不参与资源管理,不增加引用计数
		weak_ptr<ListNode> _next;
		weak_ptr<ListNode> _prev;

		//检测结点是否释放
		~ListNode()
		{
			cout << "delete" << endl;
		}
	};

	void test_shared_ptr2()
	{
		std::shared_ptr<ListNode> n1(new ListNode);
		std::shared_ptr<ListNode> n2(new ListNode);


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

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

模拟实现


	template<class T>
	class weak_ptr
	{
	public:

		// 保存资源
		weak_ptr()
			:_ptr(nullptr)
		{}

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

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


定制删除器

定制删除器的意思是,我们可以传入一个定制删除资源的方法,它就会按照我们想要删除的方式进行删除。

我们可以使用仿函数和lambda表达式来解决


template<class T>

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

int main()
{
	//仿函数
	std::shared_ptr<int> sp1(new int[10], DeleteArray<int>());
	std::shared_ptr<string> sp2(new string[10], DeleteArray<string>());
	
	//lambda表达式
	std::shared_ptr<string> sp3(new string[10], [](string* ptr) {delete[] ptr; });

	//文件类型的管理
	std::shared_ptr<FILE> sp4(fopen("test.cc", "r"), [](FILE* ptr){ fclose(ptr); });



	return 0;
}



	

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

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

相关文章

「消息中间件」Apache Kafka中的事务

在之前的一篇博客文章中&#xff0c;我们介绍了Apache Kafka的一次语义。这篇文章介绍了各种消息传递语义&#xff0c;介绍了幂等生成器、事务和Kafka流的一次处理语义。现在&#xff0c;我们将继续上一节的内容&#xff0c;深入探讨Apache Kafka中的事务。该文档的目标是让读者…

Arcgis进阶篇(7)——如何使用postgis实现要素服务,替代sde库

因为企业级地理信息数据库&#xff08;sde库&#xff09;需要官方许可&#xff08;这里不讨论破解&#xff0c;对于商业项目&#xff0c;没啥意义&#xff09;&#xff0c;所以自然的想到使用postgis平替sde库&#xff0c;虽然没有sde库那么强大和方便&#xff0c;但是能实现很…

socket套接字及TCP的实现框架

一、socket套接字 1.体系结构的两种形式 &#xff08;1&#xff09;网络的体系结构是计算机网络的各层及其协议的集合&#xff0c;就是这个计算机网络及其构件所应完成的功能的精确定义&#xff08;不涉及实现&#xff09;。 &#xff08;2&#xff09;实现是遵循这种体系结…

语言模型主流

词向量模型 bert4keras 字级 bert4keras 文档中心 bert4keras/examples at master bojone/bert4keras GitHub mirrors / bojone / bert4keras GitCode GitHub - bojone/bert4keras: keras implement of transformers for humans bert4keras、transformers 加载预训练bert…

[4]PCB设计实验|LPWAN物联网系统解决方案 |LoRa模块/LoRa网关/云平台/LoRa应用案例|9:30~10:00

目录 1.LPWAN物联网系统解决方案 LoRa模块/LoRa网关/云平台/LoRa应用案例 2.LoRaWAN网络部署情况 LoRaWAN网络架构 3.基于LPWAN技术的无线通信端到端解决方案 LoRa低功耗广域网智能终端 CY-LRW-102开关控制器 CY-LRB-101开关检测器 4.Lo…

软件生命周期( 包括各开发模型的优缺点)知识点全面

软件生命周期 指软件产品从计划到软件交付使用&#xff0c;直到最终退出为止的过程。包括计划阶段、分析阶段、实现阶段、测试阶段和运行维护阶段。 软件开发模型 瀑布模型、螺旋模型、喷泉模型、原型化模型、演化模型 瀑布模型&#xff1a;严格遵循软件生命周期各阶段的固定顺…

vue对接海康摄像头,配合ifarme进行展示。

1、在public文件夹下建一个文件ifarme.index&#xff0c;和index.html同级。 <!doctype html> <html><head><title></title><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><meta http-equi…

支付宝电脑版二维码Java

/*生成二维码/ PostMapping(value “getQRCode”) RequestLog(“支付宝支付”) ApiOperation(“支付宝支付”) AnonAccess public String qrCode(Validated RequestBody SysMemberRecordVo sysMemberRecordVo) throws AlipayApiException { AlipayClient alipayClient new De…

聚观早报|上海迪士尼辟谣乐园落户武汉;Wi-Fi7国内标准即将落地

今日要闻&#xff1a;上海迪士尼辟谣乐园将落户武汉&#xff1b;Wi-Fi7国内标准即将落地&#xff1b;微软Office365AI助手价格曝光&#xff1b;刘畊宏20秒短视频要价60万&#xff1b;滴滴旗下公司新增互联网游戏业务 上海迪士尼辟谣乐园将落户武汉 6 月 3 日&#xff0c;有消息…

Android-Activity生命周期

文章参考&#xff1a;添加链接描述 文章参考&#xff1a;添加链接描述 五大状态 StartingRunningStoppedPausedDestroyed 借用一张已经包浆的图 PS&#xff1a;Running和Paused是可视阶段&#xff0c;其余都是不可视 几大函数 onCreate&#xff1a;通过setContentLayout初始…

如何选择最佳数据库:MongoDB、PostgreSQL或ScyllaDB?

Tractian是一家提供工业监控系统的机器智能公司。去年&#xff0c;我们面临着将我们的实时机器学习&#xff08;ML&#xff09;环境和分析仪表板升级以支持数据吞吐量的大幅增长的挑战&#xff0c;因为我们成功地将客户数据库和数据量扩大了10倍。 我们意识到&#xff0c;在快…

MySQL版本5.7.99?

序&#xff1a;在项目工作中需要从三方厂商数据库同步数据到项目业务库中&#xff0c;本平平无奇的功能却被一个报错打破。 在使用某框架的DataSourceConfig(Object)方法初始化数据库连接时&#xff0c;日志输出报错&#xff1a; Unknown system variable transaction_isolatio…

sparkRDD编程实战

文章目录 sparkRDD编程实战1、Spark RDD 实现单词计数2、Spark RDD 实现分组求TopN3、Spark RDD 实现二次排序4、Spark RDD 计算平均成绩5、Spark RDD 倒排索引统计每日新增用户6、Spark案例实操7、Spark RDD 综合应用需求1&#xff1a;Top10热门品类需求说明实现方案一实现方案…

【Python SMTP/POP3/IMAP】零基础也能轻松掌握的学习路线与参考资料

Python是一种高级编程语言&#xff0c;广泛应用于Web开发、人工智能、数据科学、自动化等领域。SMTP/POP3/IMAP是与邮件相关的三个协议&#xff0c;分别用于发送邮件、接收邮件和访问邮件。使用Python可以轻松实现这些功能&#xff0c;本文将介绍Python SMTP/POP3/IMAP的学习路…

【Python】Python系列教程-- Python3 条件控制(十六)

文章目录 前言if 语句if 嵌套match...case 前言 往期回顾&#xff1a; Python系列教程–Python3介绍&#xff08;一&#xff09;Python系列教程–Python3 环境搭建&#xff08;二&#xff09;Python系列教程–Python3 VScode&#xff08;三&#xff09;Python系列教程–Pytho…

Spring Boot整合Swagger2 Swagger2配置

目录 什么是Swagger? Swagger如何使用 如何使用Swagger 查看SwaggerAPI文档 什么是Swagger? Swagger是一款流行的RESTful API文档生成工具&#xff0c;它支持多种编程语言和多种框架&#xff0c;包括但不限于Java、Python、Node.js、Go等&#xff0c;Spring Boot也提供了…

【机器学习】第二章:K近邻(分类)

系列文章目录 第二章&#xff1a;K近邻&#xff08;分类&#xff09; 相关代码地址&#xff1a;https://github.com/wzybmw888/MachineLearning.git 文章目录 系列文章目录一、最近邻算法二、最近邻算法的缺陷&#xff08;1&#xff09;策略一&#xff1a;K近邻&#xff08;k‐…

java源码为什么需要编译成字节码?

作用1: jvm支持多语言,需要字节码作为统一的规范 作用2: 字节码转成机器的指令会更快 作用3: 如果没有对应的反编译器,字节码还具有一定的安全保密作用

【Rust日报】2023-06-02 Rust 1.70.0 稳定版发布

Rust 1.70.0 稳定版发布 Rust 团队很高兴地宣布 Rust 的新版本 1.70.0。Rust 是一种编程语言&#xff0c;它使每个人都能构建可靠、高效的软件。 最大的特性是&#xff0c;OnceCell稳定版可用啦。 如果你通过 rustup 安装了以前版本的 Rust&#xff0c;你可以通过以下方式获得 …

Linux 之大数据定制篇-Shell 编程

Linux 之大数据定制篇-Shell 编程 为什么要学习Shell 编程 Linux 运维工程师在进行服务器集群管理时&#xff0c;需要编写Shell 程序来进行服务器管理。对于JavaEE 和Python 程序员来说&#xff0c;工作的需要&#xff0c;你的老大会要求你编写一些Shell 脚本进行程序或者是服…