【C++】shared_ptr线程安全问题和循环引用问题

 看下面的代码我们就会发现shared_ptr在多线程的情况下会出问题:

void test_multi_thread_copy(yan::shared_ptr<int>& sp,size_t n)//把智能指针拷贝n次
{
	for (size_t i = 0; i < n; i++)
	{
		yan::shared_ptr<int> copy(sp);
	}
}

void test_shared_ptr_safe()
{
	yan::shared_ptr<int> sp(new int);

	std::thread t1(test_multi_thread_copy, sp, 1000);
	std::thread t2(test_multi_thread_copy, sp, 1000);

	cout << sp.use_count() << endl;
	t1.join();
	t2.join();
}

此时sp1的引用计数应该从1加两次,加到3,两个线程同时对引用计数进行 ++操作时,如果两个线程同时执行加加操作的话,引用计数只加了1次,而我们期望它加两次,这样会导致最终引用计数已经为0了,该资源已被释放,但是却还有一个对象指向该资源,也就是出现野指针问题,所以不是线程安全的。

在多线程的情况下,要保证这引用计数的加加操作是原子性的,就需要加锁,完善后的代码如下:

	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcount(new int(1))//把引用计数置为1
			, _pmtx(new std::mutex)
		{}

		void AddRef()//引用计数++,加锁
		{
			_pmtx->lock();
			++(*_pcount);
			_pmtx->unlock();
		}

		void Release()//释放
		{
			bool flag = false;
			_pmtx->lock();
			if (--(*_pcount) == 0)//保证释放是线程安全的
			{
				cout << "delete: " << _ptr << endl;
				delete _ptr;
				delete _pcount;
				flag = true;
			}
			_pmtx->unlock();

			if (flag)
				delete _pmtx;

			//错误,这三个指针有可能会被释放2次,因为它是解锁之后,才判断pcount并释放的
			/*_pmtx->lock();
			--(*pcount);
			_pmtx->unlock();
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
				delete _pmtx;
			}*/

		}

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

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

		//拷贝构造-->++引用计数即可
		shared_ptr(const shared_ptr<T>& sp)//拷贝构造
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
			, _pmtx(sp._pmtx)
		{
			AddRef();
		}

		//sp1=sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)//赋值
		{
			
			
			if (_ptr!=sp._ptr)//防止自己给自己赋值
			{
				Release();//释放的是sp1

				//两个指针指向同一块空间,再++引用计数
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				_pmtx = sp._pmtx;
			
				AddRef();//增加的是sp3的引用计数
			}
			return *this;
		}
        //查看引用计数
		int use_count()
		{
			return *_pcount;
		}

		~shared_ptr()
		{
			Release();
		}
	private:
		T* _ptr;
		int* _pcount;
		std::mutex* _pmtx;
	};

加锁之后,shared_ptr的引用计数的加加和减减操作变成了原子性的。

智能指针对象本身是线程安全的,它的引用计数的加加和减减操作是加过锁的,但是它指向管理的资源不一定是线程安全的,资源通常都是动态开辟的,动态开辟在堆上,多线程通过智能指针解引用这份资源,不是线程安全的,智能指针只是可以访问这份资源,去修改资源时,并不会加锁,不是线程安全的,想要实现资源的线程安全要通过资源本身的加锁来实现。

shared_ptr的循环引用问题

shared_ptr的循环引用问题是发生在特定场景下的问题:

struct ListNode
{
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode>  _prev;

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

void test_shared_ptr_cycleRef()
{
	std::shared_ptr<ListNode> cur(new ListNode);
	std::shared_ptr<ListNode> next(new ListNode);

	cur->_next = next;
	next->_prev = cur;

}

int main()
{
	test_shared_ptr_cycleRef();
	system("pause");
	return 0;
}

在上面代码中,shared_ptr不仅管理了cur和next,同时也管理了cur里面的_prev和next里面的_next,当shared_ptr指向cur和next时,两个智能指针的引用计数都是1,再让cur的_next指向next,让next的_prev指向cur,此时两个智能指针的引用计数都变成了2,出了作用域,cur和next的生命周期结束,cur销毁,其引用计数减1,next销毁,其引用计数减1,此时next结点由cur中的_next管理,cur结点由next中的_prev管理。也就是说cur的释放取决于next,而next的释放又依赖于cur,这就是循环引用,具体过程如下图所示:

 

 

为了解决shared_ptr的循环引用问题,引入了weak_ptr,weak_ptr是一个特殊的智能指针,它不具备其它智能指针所具备的特点:RAII,像指针一样。它是专门解决shared_ptr的循环引用问题的。

struct ListNode
{
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode>  _prev;

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

weak_ptr解决循环引用的原理是:当已经有对象管理这份资源时,如果再发生赋值之类的操作生成新的对象来二次管理资源时,weak_ptr不会增加shared_ptr的引用计数,也就是不让新的对象接管这份资源。 还有一个需要注意的是:weak_ptr在常规场景下,是不能用的,它只能用在shared_ptr的循环引用的场景。

shared_ptr的定制删除器(定制删除的方式)

shared_ptr里面管理的都是单独的指针,释放时都是用delete进行释放的,如果现在使用new[]申请出来的,就应该用delete[]进行释放,资源不仅仅可以使用new和new[],还可以使用malloc,这时候,就要用对应的接口进行释放。这时shared_ptr使用定制删除器来解决此类问题,而删除器又是通过仿函数来定制的。 具体实现代码如下:

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)//仿函数
	{
		delete[] ptr;
	}
};

struct B
{
	~B()
	{
		cout << "~B()" << endl;
	}
};

//定制删除器(定制删除的方式,一般默认是delete)
void test_shared_ptr_deletor()
{
	DeleteArray<B> del;
	std::shared_ptr<B> sp(new B[10],del);
}

int main()
{

	test_shared_ptr_deletor();
	system("pause");
	return 0;
}

通过定制删除器,shared_ptr不仅可以释放new出来的,还可以释放new[]出来的,也可以释放malloc出来的,只用给定一个free的仿函数传过去即可。

C++智能指针的发展历史

所有的智能指针,除了weak_ptr,其它智能指针都符合:RAII和像指针一样这两个特性。

  1. C++ 98 中产生了第一个智能指针auto_ptr,但是后来被标记为有缺陷的智能指针.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过TR1并不是标准版。
  4. C++ boost的scoped_ptr相当于C++ 11的unique_ptr,也是防拷贝的,而boost的shared_ptr和weak_ptr和C++11中的和shared_ptr和weak_ptr作用是一样的。
  5. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值