【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>{public: constexpr 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<char> wp(new char[1024]);
更多的是按照如下的方式去获得一个weak_ptr对象。
shared_ptr<char> sp(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_ptr{
public:
typedef _Tp element_type;
constexpr __weak_ptr() noexcept
: _M_ptr(0) , _M_refcount() {} __weak_ptr(const __weak_ptr&) noexcept = default; __weak_ptr& operator=(const __weak_ptr&) noexcept = default; ~__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模块的内存管理,同时也极大降低了野指针的风险。