智能指针循环引用图解秒懂

智能指针shared_ptr

实现一个简单的shared_ptr类
#include<iostream>
#include<mutex> 

//仿函数
template<class T>
class DFDel
{
public:
	void operator()(T*& p)
	{
		if (p)
		{
			delete p;
			p = nullptr;
		}
	}
};

template<class T>
class Free
{
public:
	void operator()(T*& p)
	{
		if (p)
		{
			free(p);
			p = nullptr;
		}
	}
};

class FClose
{
public:
	void operator()(FILE*& p)
	{
		if (p)
		{
			fclose(p);
			p = nullptr;
		}
	}
};
//采用引用计数的方式 --> 可以实现资源的共享
//不是线程安全的,可以加锁解决引用计数的线程安全问题,但是不能解决对象中的内容线程安全的
namespace smart_ptr
{
	template<class T, class DF = DFDel<T>>  //第二个参数使用仿函数
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pMutex(new std::mutex())
		{
			if (_ptr)
				_pCount = new int(1);
		}

		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
			, _pMutex(new std::mutex())
		{
			AddRefCount();
		}

		//在和其他对象共享时,需要将自身对象的引用计数进行修改
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				Release();//在判断的过程中也将引用计数进行了--操作
				_ptr = sp._ptr;
				_pCount = sp._pCount;
				AddRefCount();
			}
			return *this;
		}

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

		T* operator->() const 
		{
			return _ptr;
		}
		
		~shared_ptr()
		{
			Release();
			delete _pMutex;
		}
	private:
		void Release()
		{
			_pMutex->lock();
			if (_ptr && 0 == --(*_pCount))
			{
				//delete _ptr; 为了完成其他类型的空间释放
				DF()(_ptr); // DF() 表示创建了一个无名对象,在使用无名对象调用()重载函数
				delete _pCount;
			}
			_pMutex->unlock();
		}

		void AddRefCount()
		{
			_pMutex->lock();
			if (_ptr)
				(*_pCount)++;
			_pMutex->unlock();
		}
	private:
		T* _ptr;
		int* _pCount;
		std::mutex* _pMutex; //可以进行加锁,解锁,保证其变为线程安全的
	};

	void TestFunc1()
	{
		shared_ptr<int> sp1(new int);
		shared_ptr<int> sp2(sp1);
		shared_ptr<int> sp3;
		shared_ptr<int> sp4(new int);
		shared_ptr<int> sp5(new int);
		sp3 = sp1;
		sp4 = sp2;
		sp1 = sp5;
	}
	void TestFunc2()
	{
		//shared_ptr<int> sp1(new int[10]); 
		//一段连续空间的话,可以直接使用vector来管理空间
		shared_ptr<FILE, FClose> sp2(fopen("array_test.cpp", "rb"));
		shared_ptr<int, Free<int>> sp3((int*)malloc(sizeof(int)));
		//shared_ptr<int> sp;
	}
}

仿函数的作用:实现不同类型的ptr指针的释放,比如说malloc申请出来的空间交予智能指针管理,或者FILE类型文件指针交予shared_ptr进行关闭理时,在进行释放时,调用不同的仿函数 malloc申请的调用free进行释放,FILE类型的调用fcolse进行释放,默认参数DFDel表示普通new类型的空间的释放该空间。
加锁的作用:对引用计数的增减操作都加上了锁,防止多线程环境下对引用计数的操作不当(也可以使用atomic关键字来对引用计数进行修饰)
———>后面的两个测试用例可供读者参考。

循环引用问题

循环引用具体描述:在使用shared_ptr(引用计数)的只能指针时,可能会用该只能指针对象来管理链表的结点,如果是双向链表等复杂链表,会出现下图情况👇
在这里插入图片描述
如上图所以,可以有如下代码进行验证👇

struct ListNode
{
	ListNode(int data = int())
	: pre(nullptr)
	, next(nullptr)
	, _data(data)
	{
		std::cout << "ListNode():" << this << std::endl;
	}

	~ListNode()
	{
		std::cout << "~ListNode" << std::endl;
	}
	//ListNode* _pPre;
	//ListNode* _pNext;
	//使用智能指针管理
	std::shared_ptr<ListNode> pre;
	std::shared_ptr<ListNode> next;

	int _data;
};

int main()
{
	std::shared_ptr<ListNode> sp1(new ListNode(10));
	std::shared_ptr<ListNode> sp2(new ListNode(20));
	std::cout << sp1.use_count() << std::endl;
	std::cout << sp2.use_count() << std::endl;
	
	sp1->next = sp2;
	sp2->pre = sp1;

	std::cout << sp1.use_count() << std::endl;
	std::cout << sp2.use_count() << std::endl;
}

在这里插入图片描述
可以看到程序运行到最后并没有调用析构函数,所以已经导致了内存泄漏

解决方式

将结点内部的指针改用weak_ptr智能指针进行管理
修改之后的代码如下

struct ListNode
{
	ListNode(int data = int())
	: _data(data)
	{
		std::cout << "ListNode():" << this << std::endl;
	}

	~ListNode()
	{
		std::cout << "~ListNode" << this << std::endl;
	}
	//ListNode* _pPre;
	//ListNode* _pNext;

	std::weak_ptr<ListNode> pre; //使用weak_Ptr 控制结构体中的指针
	std::weak_ptr<ListNode> next;

	int _data;
};
int main()
{
	std::shared_ptr<ListNode> sp1(new ListNode(10));
	std::shared_ptr<ListNode> sp2(new ListNode(20));
	std::cout << sp1.use_count() << std::endl;
	std::cout << sp2.use_count() << std::endl;

	sp1->next = sp2;
	sp2->pre = sp1;

	std::cout << sp1.use_count() << std::endl;
	std::cout << sp2.use_count() << std::endl;
	return 0;
}
图解
构造时:

在这里插入图片描述
第一步:构造只能指针对象,将Node结点存入,两个引用计数都+1
第二步:改变指针的指向,并且将ref_Weak的计数再+1

析构释放时:

第一步:先析构shared_ptr Node1,将其Uses进行-1操作,然后增加了自身Weak计数的-1操作然后调用Node的析构函数,将结点释放
在这里插入图片描述
第二步:在析构Node结点时,需要析构内部的空间,即,由于没有next的计数为0,所以释放next的空间,在进行释放per的空间,断开ptr的指针,然后再将其weak计数-1在完成后,代表着整个Node2释放完毕
在这里插入图片描述
第三步:释放节点Node1,将其Uses进行-1操作,然后增加了自身Weak计数的-1操作,发现自身Weak计数为0,所以将自身的weak引用计数空间释放然后调用Node的析构函数,将结点释放
在这里插入图片描述
第四步:在析构Node1结点时,需要析构内部的空间,即,在进行释放next的空间,断开ptr的指针,然后再将其weak计数-1,发现Node1的Weak计数为0,所以释放该weak空间,pre为空直接进行释放,在完成后,代表着整个Node1释放完毕
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值