《weak_ptr源码剖析》
【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模块的内存管理,同时也极大降低了野指针的风险。