STL源码阅读小记(五)——Shared_ptr

前言

感觉好久没看stl了,中间跑去看图形学入门了,图形学是真的有趣,但是也是真难,不过接着补坑,把unique_ptr的好兄弟shared_ptr也看了。

STL版本

本文所使用的stl版本为libc++ 13.0,属于LLVM项目。

LLVM项目Github

如果遇到不熟悉的宏定义可以参考文档Symbol Visibility Macros

Shared_ptr

应该算是c++中的老熟人,应该绝大部分人应该使用过,或者在面试中见过它,对它的引用计数器印象深刻吧。这次我们看看stl的源码中shared_ptr是怎么实现的。

定义,构造和赋值

定义和关键成员

template<class _Tp>
class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS shared_ptr
{
    
    
public:
#if _LIBCPP_STD_VER > 14
    typedef weak_ptr<_Tp> weak_type;
    typedef remove_extent_t<_Tp> element_type;
#else
    typedef _Tp element_type;
#endif

private:
    element_type*      __ptr_;
    __shared_weak_count* __cntrl_;

    struct __nat {
    
    int __for_bool_;};
	}

shared_ptr内部有两个比较关键的成员类型,一个是ptr,另一个是__shared_weak_count,前着存储指针,后者存储计数器,在两个源码我们等看完构造和拷贝构造函数再看吧。

__nat是用来和enable_if配合,检测自定义析构函数,但是有点疑惑为什么不在template定义位置做,而要在参数位置做,在c++11后已经支持模板函数的默认模板参数。也搜索不到有用的资料,这点等以后来验证吧。

构造函数

先看一个最简单的构造函数,是参数为原始指针的构造

template<class _Yp, class = _EnableIf<
        _And<
            __compatible_with<_Yp, _Tp>
            // In C++03 we get errors when trying to do SFINAE with the
            // delete operator, so we always pretend that it's deletable.
            // The same happens on GCC.
#if !defined(_LIBCPP_CXX03_LANG) && !defined(_LIBCPP_COMPILER_GCC)
            , _If<is_array<_Tp>::value, __is_array_deletable<_Yp*>, __is_deletable<_Yp*> >
#endif
        >::value
    > >
    explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
    
    
        unique_ptr<_Yp> __hold(__p);
        typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
        typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT > _CntrlBlk;
        __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
        __hold.release();
        __enable_weak_this(__p, __p);
    }

当参数为一个指针类型的时候,首先判断_Yp和_Tp是否为可转换,这里是智能指针为了萃取普通指针父子类可转换,以及_Tp不能为数组。

这里使用unique_ptr的原因我找了下,大致如下:

unique_ptr is used for the purpose that unique pointer is typically used: To delete the pointer when it goes out of scope. In this case, the unique pointer is always released before the constructor returns normally and in that case it won’t delete the pointer. But that won’t happen when an exception is thrown. In that case the unique pointer will still own the pointer and will delete it in its destructor before the exception propagates to the caller.

大致意思是如果在执行中抛出了异常,unique_ptr能把指针释放。

	 shared_ptr(const shared_ptr& __r) _NOEXCEPT;

template<class _Yp>
	  shared_ptr(const shared_ptr<_Yp>& __r,
		typename enable_if<__compatible_with<_Yp, element_type>::value, __nat>::type = __nat())
                       _NOEXCEPT;

拷贝构造函数

接着我们看两个很有意思的拷贝构造函数的声明

shared_ptr(const shared_ptr& __r) _NOEXCEPT;


    template<class _Yp>
        _LIBCPP_INLINE_VISIBILITY
        shared_ptr(const shared_ptr<_Yp>& __r,
                   typename enable_if<__compatible_with<_Yp, element_type>::value, __nat>::type = __nat())
                       _NOEXCEPT;

第一个为自身同类型的拷贝构造,很容易看懂,而第二个是实现普通指针子类可以转为父类的拷贝构造。在参数位置使用默认参数进行SFINAE,理论上应该也是能放到默认模板位置。主要就算检查一下两种类型是否可以转换。

它们的定义几乎一样,所以就列一个啦

template<class _Tp>
inline
shared_ptr<_Tp>::shared_ptr(const shared_ptr& __r) _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    
    
    if (__cntrl_)
        __cntrl_->__add_shared();
}

可以发现在执行拷贝构造函数时会复制ptr和计数器,然后对自身计数器加1。

这里的__add_shared()函数内部使用了

__libcpp_atomic_refcount_increment(__shared_owners_);

template <class _Tp>
inline _LIBCPP_INLINE_VISIBILITY _Tp
__libcpp_atomic_refcount_increment(_Tp& __t) _NOEXCEPT
{
    
    
#if defined(_LIBCPP_HAS_BUILTIN_ATOMIC_SUPPORT) && !defined(_LIBCPP_HAS_NO_THREADS)
    return __atomic_add_fetch(&__t, 1, __ATOMIC_RELAXED);
#else
    return __t += 1;
#endif
}

保证计数器的原子性,但是要注意的是shared_ptr不保证ptr的原子性。

引用计数器

__shared_weak_count

从前面shared_ptr的成员声明我们发现计数器类型是一个__shared_weak_count。
首先__shared_weak_count继承__shared_count。

class _LIBCPP_TYPE_VIS __shared_weak_count
    : private __shared_count
{
    
    
    long __shared_weak_owners_;
}
class _LIBCPP_TYPE_VIS __shared_count
{
    
    
    long __shared_owners_;
}

__shared_weak_count拥有_shared_weak_owners,而它的父类则拥有_shared_owners

__shared_weak_count内部有两个关键的add函数

void
__shared_count::__add_shared() noexcept
{
    
    
    __libcpp_atomic_refcount_increment(__shared_owners_);
}
void
__shared_weak_count::__add_weak() noexcept
{
    
    
    __libcpp_atomic_refcount_increment(__shared_weak_owners_);
}

__add_shared函数被shared_ptr使用,而__add_weak被weak_ptr使用。

__shared_ptr_pointer

但是,但是,但是重要的事情要说三次,__shared_weak_count并非shared_ptr实际上用到,实际上__shared_weak_count是一个父类,它的子类__shared_ptr_pointer才是shared_ptr真正使用的计数器,对于父类,__shared_ptr_pointer增加了指针,删除器以及分配器的存储。

我们先看类内成员

template <class _Tp, class _Dp, class _Alloc>
class __shared_ptr_pointer
    : public __shared_weak_count
{
    
    
    __compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;
}

熟悉的老朋友__compressed_pair,在看shared_ptr中的内存申请

typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
        typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT > _CntrlBlk;
        __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());

weak_ptr

weak_ptr和shared_ptr基本一样,weak_ptr在构造时候会将shared_ptr的两个成员变量复制过来,从本质上来说weak_ptr在计数时加到一个和shared_ptr不同的计数上。

成员:

template<class _Tp>
class _LIBCPP_SHARED_PTR_TRIVIAL_ABI _LIBCPP_TEMPLATE_VIS weak_ptr
{
    
    
public:
    typedef _Tp element_type;
private:
    element_type*        __ptr_;
    __shared_weak_count* __cntrl_;
}

从shared_ptr构造

template<class _Tp>
template<class _Yp>
inline
weak_ptr<_Tp>::weak_ptr(shared_ptr<_Yp> const& __r,
                        typename enable_if<is_convertible<_Yp*, _Tp*>::value, __nat*>::type)
                         _NOEXCEPT
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    
    
    if (__cntrl_)
        __cntrl_->__add_weak();
}

Supongo que te gusta

Origin blog.csdn.net/ninesnow_c/article/details/124321990
Recomendado
Clasificación