当前位置: 首页 > article >正文

【C++】智能指针:auto_ptr、unique_ptr、share_ptr、weak_ptr(技术介绍 + 代码实现)(待更新)

文章目录

  • 0. 概述
    • 智能指针,智能在哪儿?
    • RAII 的介绍
    • 四个智能指针的特点:
  • 1. auto_ptr(C++98)
      • 🐎核心功能的简单实现
  • 2. unique_ptr(C++11)
      • 🐎核心功能的简单实现
  • 3. shared_ptr(C++11)
      • 🐎核心功能的简单实现


0. 概述

智能指针,智能在哪儿?

  • 使用了模板类,建立的是 智能指针对象,自动调用智能指针类型的构造和析构函数。也就是说,对于动态开辟的空间如果用智能指针保存,就不需要手动释放啦,极大程度降低了内存泄漏的风险。
    • 这样利用对象生命周期进行程序资源控制的技术就是 RAII。
  • *-> 的重载,使 智能指针对象 具有指针的行为能力,能让用户像使用指针一样的使用。

RAII 的介绍

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

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

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

四个智能指针的特点:

我把四个智能指针的特点介绍在前面,你若还有什么细节问题再去具体的栏目下翻找吧~

  • auto_ptr:管理权转移,通过拷贝构造函数和赋值重载函数来实现。
    • 原对象拷贝给新对象的时候,原对象就会被设置为nullptr,此时就只有新对象指向一块资源空间。
    • 会出现指针悬空问题。
  • unique_ptr:禁用拷贝构造和赋值构造
    • unique_ptr(unique_ptr&) = delete;
    • operator=(unique_ptr&) = delete;
  • share_ptr:引用计数
    • 计数的对象在堆上,所有线程都能访问,因此需要锁保证其安全性
    • 会出现循环引用的问题
  • weak_ptr:弱关联性
    • weak_ptr 类的对象它可以指向 shared_ptr,并且不会改变 shared_ptr 的引用计数

1. auto_ptr(C++98)

核心功能:管理权转移

管理权转移的同时也会导致 原指针悬空,容易造成野指针问题,不推荐使用。

🐎核心功能的简单实现

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

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 无力吐槽的神拷贝...
		// 管理权转移:导致的是原来的指针悬空,很多公司明令禁止使用 auto_ptr
		auto_ptr(auto_ptr<T>& ap)
			:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}
		
		T* operator=(auto_ptr<T>& ap)
		{
			T* tmp = ap._ptr;
			ap._ptr = nullptr;
			return tmp;
		}

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

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
--------------------------------------------------
	void test_auto()
	{
		auto_ptr<int> ap1(new int(1));
		auto_ptr<int> ap2(ap1);

		*ap1 = 1; // err...管理权转移以后导致ap1悬空,不能访问
		*ap2 = 1;
	}
}

2. unique_ptr(C++11)

核心功能:防拷贝(= delete 声明拷贝构造和复制重载)

unique_ptr 的指针,简单粗暴,是防了拷贝,不过也只解决了不需要拷贝的场景。
(ps:从 boost 里面吸收来的)
(pps:需要拷贝的场景就需要使用到接下来会介绍的 shared_ptr 和 weak_ptr 了)

🐎核心功能的简单实现

namespace ttang
{
	template<class T>
	class unique_ptr
	{
	private:
		T* _ptr;
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

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

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

		// C++11思路:设置不许再实现了,语法直接支持的(不需要私有了)
		unique_ptr(const unique_ptr<T>& up) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;	// 严格来说赋值也封了更好一点

		// C++98思路:只声明不实现,但是用的人可能会在外面强行定义,所以再加一条,声明为私有
	//private:
		// unique_ptr(const unique_ptr<T>& up);
		// unique_ptr<T>& operator=(const unique_ptr<T>& up);
	};
--------------------------------------------------
	void test_unique()
	{
		unique_ptr<int> up1(new int(1));
		unique_ptr<int> up2(up1);	// err...
	}
}

3. shared_ptr(C++11)

核心功能:引用计数

之前在一开始的概述部分介绍了两个智能指针为什么智能的原因,走到了 shared_ptr,我们的智能指针就真的更神了,他甚至还引申出 智能指针三大件 的说法:

  • RAII
  • 想指针一样使用
  • 可以拷贝(浅拷贝!!)

他可以对同一个对象拷贝的同时,还可以在最后一个智能指针使用完毕后调用其析构函数,使用的是引用计数的技术。

在具体对 shared_ptr 实现之前,对于这里的 引用计数,可以稍微探讨一下:

1)如果要使用引用计数,设置一个静态变量 count 行不行呢?不行,因为静态变量属于所有对象。而 每实例化一个对象都可能多有个资源,每个资源应该配对一个引用计数

2)如果是多个线程去调用引用计数,还需要保证其线程安全,那就加个锁吧。

3)计数加锁后,shared_ptr 本身就会是线程安全的,但是他生成的对象不是线程安全的。

🐎核心功能的简单实现

namespace ttang
{
	template<class T>
	class shared_ptr
	{
	private:
		T* _ptr;
		int* _pcount;
		mutex* _pmtx;								// 锁也得是指针,因为是多个指针指向同一把锁
		function<void(T*)> _del = [](T* ptr) {		// 解决对 deletor 的保存问题,需要一个缺省的!!
			cout << "lambda delete:" << ptr << endl;
			delete ptr;
		};
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))	// 每个资源都分配一个引用计数count
			, _pmtx(new mutex)		// 每个资源都有一把锁,保证自己资源计数安全
		{}

		// 定制删除器(通过仿函数实现的!--是可调用对象,所以我们拿的一个function定义_del)
		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new int(1))
			, _pmtx(new mutex)
			, _del(del)
		{}
		
		~shared_ptr()
		{
			Release();
		}

		void Release()
		{
			_pmtx->lock();

			bool deleteFlag = false;

			if (--(*_pcount) == 0)
			{
				if (_ptr)
				{
					//cout << "delete:" << _ptr << endl;
					//delete _ptr;
					_del(_ptr);			// 如果_del 不给缺省的话,这里默认的构造可能会出问题
				}
				delete _pcount;

				deleteFlag = true;
				// delete _pmtx;  锁也要释放的呀,可以下面又要解锁。如何解决?
			}

			_pmtx->unlock();

			if (deleteFlag)
			{
				delete _pmtx;
			}
		}

		void AddCount()
		{
			_pmtx->lock();

			++(*_pcount);

			_pmtx->unlock();
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			AddCount();
		}

		// 正常赋值:
		// sp1 = sp4;					被赋值sp1的肯定要--,sp4要++
		// 自己给自己赋值:
		// sp1 = sp1;					自己给自己	
		// sp1 = sp2;(管理同样资源)	也是自己给自己	if(&sp != this)不得行哦,防不了这一种
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();

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

				AddCount();
			}

			return *this;
		}

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

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

		T* get()
		{
			return _ptr;
		}

		int use_count()
		{
			return *_pcount;
		}
	};
}



	// 跟库里不一样,简易版的蛤
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

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

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

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

		T* get()
		{
			return _ptr;
		}

	private:
		T* _ptr;
	};





	void test_shared()
	{
		shared_ptr<int> sp1(new int(1));
		shared_ptr<int> sp2(sp1);
		shared_ptr<int> sp3(sp2);

		shared_ptr<int> sp4(new int(10));

		//sp1 = sp4;
		sp4 = sp1;

		sp1 = sp1;
		sp1 = sp2;
	}




	// 多线程的问题!!!!!!!
	struct Date
	{
		int _year = 0;
		int _month = 0;
		int _day = 0;
	};

	// shared_ptr本身是线程安全的,因为计数是加锁保护
	// shared_ptr管理的对象是否是线程安全?不是
	void SharePtrFunc(ttang::shared_ptr<Date>& sp, size_t n, mutex& mtx)
	{
		//cout << sp.get() << endl;
		//cout << &sp << endl;

		for (size_t i = 0; i < n; ++i)
		{
			// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。
			ttang::shared_ptr<Date> copy(sp);

			mtx.lock();

			sp->_year++;
			sp->_day++;
			sp->_month++;

			mtx.unlock();
		}
	}

	void test_shared_safe()
	{
		ttang::shared_ptr<Date> p(new Date);
		cout << p.get() << endl;

		const size_t n = 10000;
		mutex mtx;
		thread t1(SharePtrFunc, ref(p), n, ref(mtx));	// 线程中以引用传递对象参数,必须加一个ref(),是一个库函数,否则会认为是传值传参会报错。
		thread t2(SharePtrFunc, ref(p), n, ref(mtx));	// 13 底下可以检测,19 是直接报错
		//cout << &p << endl;	

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

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

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


	
	// 循环引用的问题!!!!!!!
	struct ListNode
	{
		/*ListNode* _next;
		ListNode* _prev;*/

		//ttang::shared_ptr<ListNode> _next;
		//ttang::shared_ptr<ListNode> _prev;

		ttang::weak_ptr<ListNode> _next;		// weak_ptr解决循环引用的问题,他可以指向资源,但是他不参与管理,不增加应用计数
		ttang::weak_ptr<ListNode> _prev;
		int _val;

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

	// 循环引用场景:
	// 有智能指针 n1 和 n2 指向结构体,里面包含的智能指针又相互指了!!出现闭环,导致_count无法到0无法析构!!
	// 循环引用--》内存泄漏
	// 主要原理:成员的生命周期,取决于对象的生命周期,对象的生命周期结束则成员调用析构函数,成员源于被另一个智能指针管理,无法释放,形成闭环。
	void test_shared_cycle()
	{
		/*ListNode* n1 = new ListNode;
		ListNode* n2 = new ListNode;

		n1->_next = n2;
		n2->_prev = n1;		// 会出现抛异常未释放的情况,所以我们现在并不推荐这么写!
							// 那怎么写呢?--》智能指针~
		delete n1;
		delete n2;*/

		ttang::shared_ptr<ListNode> n1(new ListNode);	// std 里面只能这样显示的调构造
		ttang::shared_ptr<ListNode> n2(new ListNode);

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

		n1->_next = n2;		// 链接的时候,智能指针(n2)怎么赋值给原生指针(_next)呢?把ListNode里的指针改成智能指针就好了!
		n2->_prev = n1;	

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

	// weak_ptr
	// 1、他不是常规的智能指针,不支持RAII
	// 2、支持像指针一样
	// 3、专门设计出来,辅助解决shared_ptr的循环引用问题
	//    weak_ptr可以指向资源,但是他不参与管理,不增加引用计数


	
	//  定制删除器 -- 可调用对象
	template<class T>
	struct DeleteArray
	{
		void operator()(T* ptr)
		{
			cout << "void operator()(T* ptr)" << endl;
			delete[] ptr;
		}
	};

	//void test_shared_deletor()
	//{
	//	std::shared_ptr<Date> spa1(new Date[10], DeleteArray<Date>());
	//	std::shared_ptr<Date> spa2(new Date[10], [](Date* ptr){
	//		cout << "lambda delete[]"<<ptr << endl;
	//		delete[] ptr; 
	//	});

	//	std::shared_ptr<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr){
	//		cout << "lambda fclose" << ptr << endl;
	//		fclose(ptr);
	//	});
	//}


	void test_shared_deletor()
	{
		ttang::shared_ptr<Date> sp0(new Date);

		ttang::shared_ptr<Date> spa1(new Date[10], DeleteArray<Date>());
		ttang::shared_ptr<Date> spa2(new Date[10], [](Date* ptr) {
			cout << "lambda delete[]:" << ptr << endl;
			delete[] ptr;
			});

		ttang::shared_ptr<FILE> spF3(fopen("Test.cpp", "r"), [](FILE* ptr) {
			cout << "lambda fclose:" << ptr << endl;
			fclose(ptr);
			});
	}
}






// 智能指针的个间区别 unique、share、weak

http://www.kler.cn/news/108079.html

相关文章:

  • Megatron-LM GPT 源码分析(三) Pipeline Parallel分析
  • AWS SAP-C02教程11-解决方案
  • C#,数值计算——分类与推理,基座向量机的 Svmgenkernel的计算方法与源程序
  • 中微爱芯74逻辑兼容替代TI/ON/NXP工规品质型号全
  • 【杂记】Ubuntu20.04装系统,安装CUDA等
  • python爬虫之feapder.AirSpider轻量爬虫案例:豆瓣
  • PHP简单实现预定义钩子和自定义钩子
  • Linux国产系统无法连接身份证读卡器USB权限解决办法
  • nrf52832 开发板入手笔记:J-Flash 蓝牙协议栈烧写
  • Nginx 的配置文件(负载均衡,反向代理)
  • Spring Security: 整体架构
  • uniapp-图片压缩(适配H5,APP)
  • 10月Java行情 回暖?
  • 【机器学习可解释性】4.SHAP 值
  • 第10期 | GPTSecurity周报
  • scratch接钻石 2023年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析
  • 力扣第763题 划分字母区间 c++ 哈希 + 双指针 + 小小贪心
  • 制作自己的前端组件库并上传到npm上
  • MySQL实战2
  • 华为c语言编程规范
  • 【Unity】RenderFeature应用(简单场景扫描效果)
  • Linux学习第26天:异步通知驱动开发: 主动
  • 基于Headless构建高可用spark+pyspark集群
  • React中useEffect Hook使用纠错
  • 大彩串口屏读写文件问题
  • Proteus仿真--从左往右流水灯仿真(仿真文件+程序)
  • React之如何捕获错误
  • PlantSimulation访问本地Excel文件的方法
  • 10分钟了解JWT令牌 (JSON Web)
  • 目标检测YOLO实战应用案例100讲-改进YOLOv4的遥感图像目标检测