【C++】智能指针(后续)

上篇文章我们讲了几种智能指针原理以及实现方法,这篇文章我们主要讲一下shared_ptr的线程安全问题和循环引用问题。

目录

 

一.线程安全问题

 二.循环引用

一.线程安全问题

shared_ptr的线程安全问题存在两方面

1.由于shared_ptr的原理是多个对象共用引用计数,那么在两个线程的智能指针同时进行++或者--操作时就会出现问题。假设引用计数的初始值是1,在两个线程同时++时就有可能使加完后的引用计数是2。这时候引用计数会发生错乱,会导致资源未释放或者程序奔溃的情况。所以引用计数的++或者--必须加锁,这样才能保证线程安全。(可以联系线程安全的同步与互斥)

2.如果智能指针管理的对象放在堆上,两个线程中同时访问,会导致线程出现问题(临界资源争抢问题)

解决方法:在需要加减引用计数的地方加锁 

代码展示:

template<class T>
class Shared_Ptr
{
public:
	Shared_Ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, pCount(new int(1))
		, pmutex(new mutex)
	{
		if (_ptr == nullptr)
			*pCount = 1;
	}
	~Shared_Ptr()
	{
		//加锁
		pmutex->lock();
		--(*pCount);
		//解锁
		pmutex->unlock();
		if (_ptr && pCount == 0)
		{
			delete _ptr;
			delete pCount;
			delete pmutex;
		}
	}

	Shared_Ptr(Shared_Ptr<T>& cp)
	{
		_ptr = cp._ptr;
		pCount = cp.pCount;
		pmutex = cp.pmutex;
		if (_ptr)
		{
			pmutex->lock();
				++(*pCount);
		}
		pmutex->unlock();
	
	}

	Shared_Ptr<T>& operator=(Shared_Ptr<T> ap)
	{
		if (_ptr != ap._ptr)
		{
			//释放旧资源
			if (_ptr && pCount == 0)
			{
				delete _ptr;
				delete pCount;
				delete pmutex;
			}
			
			_ptr = ap._ptr;
			pCount = ap.pCount;
			pmutex = ap.pmutex;

			if (_ptr)
			{
				//加锁
				pmutex->lock();
				++(*pCount);
			}
			//解锁
			pmutex->unlock();
		}
		return *this;
	}

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

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

	int UseCount()
	{
		return *pCount;
	}
private:
	T* _ptr;
	int* pCount;
	mutex* pmutex;
};

 二.循环引用

1.当两个智能指针指向两个节点(node1,node2),他们各自的引用计数+1,但是当node1的next指向node2,node2的prev指向node1,只是时候他们各自的引用计数会变为2。就是这种情况下在析构的时候会产生循环引用的情况。

  • node1和node2在析构后,引用计数变为1,但是他们各自的next,prev还指向下一个节点
  • 要想释放调node1就必须析构掉node2,因为node2的prev还指向node1,要想释放掉node2就必须析构掉node1,因为node1的next还指向node2
  • 就是这时候会产生循环引用

注意:这里的循环引用和拷贝构造中不添加引用的话也会产生循环引用

图解

 node1和node2对象析构使引用计数减1,是因为出了函数作用域会自动调用析构函数使引用计数减一。这时候引用计数变为1。而要想释放这两个节点就会产生循环引用

struct ListNode
{
	shared_ptr<ListNode> prev;
	shared_ptr<ListNode> next;
	int data;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->next = node2;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node2->prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	
	system("pause");
	return 0;
}

2.解决方法

  • 在引用计数的 场景下,把节点中的prev,next改为weak_ptr
  • 原理:在一个节点指向另一个节点的时候不会使引用计数+1

注意: 虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出现了循环引用, 还是会造成内存泄漏.

struct ListNode
{
	weak_ptr<ListNode> prev;
	weak_ptr<ListNode> next;
	int data;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->next = node2;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;

	node2->prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	
	system("pause");
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值