《weak_ptr源码剖析》

73 阅读5分钟
【weak_ptr导读】 weak_ptr是一种比较特殊的智能指针,不像unique_ptr、shared_ptr一样可以托管原始的指针,它更多扮演着辅助的角色,配合shared_ptr去使用,那它背后的原理是怎样的?

**1. weak_ptr初探 **  

weak_ptr不像shared_ptr、unique_ptr一样,有独立的.h文件和.cpp文件加以实现,首先可以看weak_ptr的源码(libstdc++-v3\include\bits\shared_ptr.h)。

template<typename _Tp>class weak_ptr : public __weak_ptr<_Tp>{publicconstexpr weak_ptr() noexcept    : __weak_ptr<_Tp>()  {  }    template<typename _Tp1, typename = typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>  weak_ptr(const weak_ptr<_Tp1>& __r) noexcept    : __weak_ptr<_Tp>(__r)  {  }    template<typename _Tp1, typename = typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>  weak_ptr(const shared_ptr<_Tp1>& __r) noexcept    : __weak_ptr<_Tp>(__r)  {  }    template<typename _Tp1>  weak_ptr& operator=(const weak_ptr<_Tp1>& __r) noexcept  {	this->__weak_ptr<_Tp>::operator=(__r);	return *this;  }    template<typename _Tp1>  weak_ptr& operator=(const shared_ptr<_Tp1>& __r) noexcept  {	this->__weak_ptr<_Tp>::operator=(__r);	return *this;  }    shared_ptr<_Tp> lock() const noexcept  {       return shared_ptr<_Tp>(*this, std::nothrow);  }}

     从weak_ptr的实现可以看出,它暴露给外部的接口,只有基本的构造、拷贝赋值以及大家耳熟能详的lock接口,该类内部并没有指针成员及引用计数的成员,因为我们是没法按照下面这种方式去构造一个weak_ptr对象出来的,或者直接使用*获取weak_ptr托管的内存资源。

weak_ptr<charwp(new char[1024]);

      更多的是按照如下的方式去获得一个weak_ptr对象。

shared_ptr<charsp(new char[1024]);weak_ptr<char> wp = sp;

      把一个shared_ptr拷贝赋值给weak_ptr,内部也是调用__weak_ptr模板类的拷贝构造函数。那么我们进一步剖析下__weak_ptr的源码。

2. __weak_ptr初探


template<typename _Tp, _Lock_policy _Lp>class __weak_ptrpublic:    
    typedef _Tp element_type;        
    constexpr __weak_ptr() noexcept   
    : _M_ptr(0)  , _M_refcount()    {}        __weak_ptr(const __weak_ptr&) noexceptdefault;        __weak_ptr& operator=(const __weak_ptr&) noexceptdefault;        ~__weak_ptr() = default;	    template<typename _Tp1, typename = typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>    __weak_ptr(const __weak_ptr<_Tp1, _Lp>& __r) noexcept	: _M_refcount(__r._M_refcount)    { _M_ptr = __r.lock().get(); }	    template<typename _Tp1, typename = typename std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>    __weak_ptr(const __shared_ptr<_Tp1, _Lp>& __r) noexcept	: _M_ptr(__r._M_ptr)	, _M_refcount(__r._M_refcount)    {}	    template<typename _Tp1>    __weak_ptr& operator=(const __weak_ptr<_Tp1, _Lp>& __r) noexcept    {	_M_ptr = __r.lock().get();	_M_refcount = __r._M_refcount;	return *this;    }        template<typename _Tp1>    __weak_ptr& operator=(const __shared_ptr<_Tp1, _Lp>& __r) noexcept    {	_M_ptr = __r._M_ptr;	_M_refcount = __r._M_refcount;	return *this;    }        __shared_ptr<_Tp, _Lp> lock() const noexcept    {         return __shared_ptr<element_type, _Lp>(*this, std::nothrow);    }        long use_count() const noexcept    {         return _M_refcount._M_get_use_count();     }	    bool expired() const noexcept    {         return _M_refcount._M_get_use_count() == 0;     }        template<typename _Tp1>    bool owner_before(const __shared_ptr<_Tp1, _Lp>& __rhs) const    {         return _M_refcount._M_less(__rhs._M_refcount);     }        template<typename _Tp1>    bool owner_before(const __weak_ptr<_Tp1, _Lp>& __rhs) const    {         return _M_refcount._M_less(__rhs._M_refcount);     }        void reset() noexcept    {         __weak_ptr().swap(*this);     }
    void swap(__weak_ptr& __s) noexcept    {	std::swap(_M_ptr, __s._M_ptr);	_M_refcount._M_swap(__s._M_refcount);    }    private:    // Used by __enable_shared_from_this.    void _M_assign(_Tp* __ptr, const __shared_count<_Lp>& __refcount) noexcept    {	_M_ptr = __ptr;	_M_refcount = __refcount;    }        template<typename _Tp1, _Lock_policy _Lp1> friend class __shared_ptr;    template<typename _Tp1, _Lock_policy _Lp1> friend class __weak_ptr;    friend class __enable_shared_from_this<_Tp, _Lp>;    friend class enable_shared_from_this<_Tp>;        /* 虽然weak_ptr不托管内存,但内部还需要维护一份内的副本_M_ptr       从shared_ptr拷贝构造weak_ptr时,_M_ptr便指向shared_ptr托管的内存地址,       仅仅是浅拷贝而已。调用lock接口将weak_ptr转换成shared_ptr,       也只是将_weak_ptr管理的_M_ptr转换成shared_ptr维护的_M_ptr而已,       同样不涉及深拷贝。                                                                   */    _Tp* _M_ptr;       __weak_count<_Lp> _M_refcount;  // Reference counter.}

__weak_ptr有两个成员变量,托管的普通内存指针_M_ptr、弱引用计数_M_refcount(类型为__weak_count),__weak_ptr的弱引用计数_M_refcount一般是从__shared_ptr或者其它的__weak_ptr的_M_refcount构造而来,正因为如此,用shared_ptr初始化一个weak_ptr并不会再增加shared_ptr托管内存的引用计数。既然拷贝构造不会增加托管内存的引用计数,那weak_ptr的lock方法会增加托管内存的引用计数吗?答案是会的哈!且看如下示例:

图片

调用lock接口,归根结底就是将weak_ptr构造出一个shared_ptr对象出来,回顾上一节《shared_ptr源码剖析》,shared_ptr的构造函数最终会调用到__shared_ptr的构造函数。

shared_ptr(const weak_ptr<_Tp>& __r, std::nothrow_t)    : __shared_ptr<_Tp>(__r, std::nothrow){}

__shared_ptr构造函数的源码如下:

__shared_ptr(const __weak_ptr<_Tp, _Lp>& __r, std::nothrow_t)    : _M_refcount(__r._M_refcount, std::nothrow){    _M_ptr = _M_refcount._M_get_use_count() ?               __r._M_ptr : nullptr;}

  weak_ptr的lock接口,最终会用_weak_count去构造一个__shared_count,如果引用计数不为0就正常构造一个shared_ptr出来。那当前shared_ptr的引用计数为何会增加1呢?且看__shared_count的构造函数。

template<_Lock_policy _Lp>inline __shared_count<_Lp>::__shared_count(const __weak_count<_Lp>& __r, std::nothrow_t)    :_M_pi(__r._M_pi){    if (_M_pi != nullptr)        if (!_M_pi->_M_add_ref_lock_nothrow())            _M_pi = nullptr;}
template<>inline bool _Sp_counted_base<_S_single>::_M_add_ref_lock_nothrow(){    if (_M_use_count == 0)        return false;    ++_M_use_count;    return true;}

__shared_count的构造函数,最终会调用它的成员变量``_M_pi的 _M_add_ref_lock_nothrow接口,进行引用计数的递增操作,也即同一块内存是共用一个_Sp_counted_base的。调用weak_ptr的lock接口,最终还是会增加初始化weak_ptr的shared_ptr对象托管内存的引用计数。即解释了为啥lock接口之后,shared_ptr的引用计数会增加1。那么weak_ptr的引用计数为何也递增1呢?切看weak_ptr的use_count()接口。

long use_count() const noexcept{     return _M_refcount._M_get_use_count(); }

`    _M_refcount是__weak_count类型的成员变量,那么进一步剖析__weak_count的源码。  ``

3. __weak_count初探

template<_Lock_policy _Lp>class __weak_count{ public:   constexpr __weak_count() noexcept : _M_pi(0)   { }      __weak_count(const __shared_count<_Lp>& __r) noexcept      : _M_pi(__r._M_pi)   {      if (_M_pi != 0)        _M_pi->_M_weak_add_ref();   }      ......      ~__weak_count() noexcept   {      if (_M_pi != 0)        _M_pi->_M_weak_release();   }      long _M_get_use_count() const noexcept    {       return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0;     }     private:      friend class __shared_count<_Lp>;      _Sp_counted_base<_Lp>*  _M_pi; };

从__weak_count的源码可以看出,获取weak_ptr引用计数的接口,最终还是会调用_Sp_counted_base维护的引用计数,该引用计数和shared_ptr是同一份。那么现在大家应该知道为啥weak_ptr lock()之后,weak_ptr的引用计数也会递增1了。 ``

**4. 总结  **

1、weak_ptr更多是对shared_ptr托管内存的一个弱引用,不参与托管内存的生命周期的管理,从__weak_ptr的析构函数可以看出,它并没有对它维护的_M_ptr 执行释放操作。    2、weak_ptr的应用场景,在跨模块传递内存时,A模块申请的内存想传递给B模块使用时,B模块可以使用weak_ptr去接管这份内存,如果A模块没有释放这份内存,那么B模块就拿来使用;反之就不用,B模块不参与A模块的内存管理,同时也极大降低了野指针的风险。