您的当前位置:首页正文

shared_ptr代码研究

2024-11-28 来源:个人技术集锦

个人随笔 (Owed by: 春夜喜雨 http:///chunyexiyu)

注:研究代码是在windows上vs2015中的实现

引言

shared_ptr被使用的很广泛,许多经典库使用share_ptr作为实现函数的接口类型使用,确实还是很有必要仔细研究一下;
下面就从代码层来看一看shared_ptr的数据结构情况。

一:代码研究

1. shared_ptr继承自_Ptr_base
// CLASS TEMPLATE shared_ptr
template<class _Ty>
class shared_ptr : public _Ptr_base<_Ty> 
{...}
2._Ptr_base是一个基类,没有继承;_Ptr_base含有两个指针元素,一个指向元素,一个指向计数类;
// CLASS TEMPLATE _Ptr_base
template<class _Ty>
class _Ptr_base
{
...
private:
    element_type * _Ptr{nullptr};
    _Ref_count_base * _Rep{nullptr};
...
}
3. shared_ptr中,通过new 方法生成计数类对象
_Set_ptr_rep_and_enable_shared(_Px, new _Ref_count<_Ux>(_Px));
_Set_ptr_rep_and_enable_shared(_Px, new _Ref_count_resource<_UxptrOrNullptr, _Dx>(_Px, _STD move(_Dt)));
using _Refd = _Ref_count_resource_alloc<_UxptrOrNullptr, _Dx, _Alloc>;
4. _Ref_count_base是一个基类,无继承;_Ref_count_base有两个atomic元素,一个是_Uses, 一个是_Weaks;
// CLASS _Ref_count_base
class __declspec(novtable) _Ref_count_base
{
...
     _Atomic_counter_t _Uses;
   _Atomic_counter_t _Weaks;
   _Ref_count_base() : _Uses(1), _Weaks(1)	// non-atomic initializations
      {// construct}
...
}
5._Ref_count继承自_Ref_count_base;并有了一个指针元素,_Ptr,指向了计数的元素;
template<class _Ty>
class _Ref_count : public _Ref_count_base
{
...
    _Ty * _Ptr;
    virtual void _Destroy() noexcept override
    {	// destroy managed resource
	  delete _Ptr;
     }
...
}
6._Ref_count_resource也继承自_Ref_count_base;并有了一个_Compressed_pair元素,pair中一个是default_delete函数,一个是元素指针;
template<class _Resource,class _Dx>
class _Ref_count_resource : public _Ref_count_base
{
...
    _Compressed_pair<_Dx, _Resource> _Mypair;
     virtual void _Destroy() noexcept override
     {	// destroy managed resource
	_Mypair._Get_first()(_Mypair._Get_second());
     }
...
}

二:share_ptr综述

基于上面的代码跟踪分析,可以看出share_ptr构建时:

  • 会构建出两个指针,一个用来指向维护的元素,一个用来指向元素的计数管理类对象;
  • 看到计数类使用的计数对象是atomic类型的,所以这个计数是可以跨线程保证计数操作原子性,也即能保证线程安全;
  • 还看到计数类中有元素的指针,这个保证了计数对象可以脱离shared_ptr对象而存在,保证了shared_ptr在做数据转移时,计数对象不会丢失指向;

另外为什么shared_ptr经常会被一些流行库用作接口类型呢?

  • 一方面作为智能指针,解决了申请内存释放处理,传递过程中不用担心未被释放;
  • 另一方面shared_ptr的内部数据结构很简单,只有两个指针,shared_ptr作为参数传递时,开销很小;

三:结合make_shared一块来看

另外结合make_shared一块来看这个shared_ptr的话,也能看出直接使用make_shared的话,实际采用的是计数和对象绑定的计数类:

// FUNCTION TEMPLATE make_shared
template<class _Ty, class... _Types>
_NODISCARD inline shared_ptr<_Ty> make_shared(_Types&&... _Args)
{// make a shared_ptr
    const auto _Rx = new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);
    shared_ptr<_Ty> _Ret;
    _Ret._Set_ptr_rep_and_enable_shared(_Rx->_Getptr(), _Rx);
    return (_Ret);
}

因为是捆绑申请,内存在一起的,所以就不能分别释放,而是只能在一起释放;从_Ref_count_obj的_Destory函数中就可以看出来,它只调用资源的析构函数,并不去做delete操作的。

template<class _Ty>
class _Ref_count_obj	: public _Ref_count_base{
	virtual void _Destroy() noexcept override
		{	// destroy managed resource
		    _Getptr()->~_Ty();
		}

	virtual void _Delete_this() noexcept override
		{	// destroy self
		    delete this;
	    }
     ...
}

个人随笔 (Owed by: 春夜喜雨 http:///chunyexiyu)

显示全文