在gnu c++中讨论shared_ptr和weak_ptr只需要讨论__shared_count和__weak_count,另外就是weak_ptr没有定义operator ->和operator *,所以没有想原始指针那样的行为。
首先由简入繁。从简单的weak_ptr开始看起,然后研究shared_ptr
文章目录类图__weak_count
1、`weak_ptr`->`weak_ptr`:2、`shared_ptr`->`weak_ptr`: __shared_count
1、`raw ptr`->`shared_ptr`2、`shared_ptr`->`shared_ptr`3、`weak_ptr`->`shared_ptr` _Sp_counted_base
1、调用`_M_weak_release()`2、调用`_M_release()` enable_shared_from_this
总结weak_ptr的作用
类图 __weak_count
class __weak_count { ... private: friend class __shared_count<_Lp> ; _Sp_counted_base<_Lp> * _M_pi;};
__weak_count只有一个成员变量,这是一个多态指针。
void _M_swap(__weak_count& __r) noexcept { _Sp_counted_base<_Lp> * __tmp = __r._M_pi; __r._M_pi = _M_pi; _M_pi = __tmp;}
可以看到两个weak_prt的交换,实际上交换了相互的_M_pi这个多态指针
weak_ptr没有接受原始指针的构造,是能通过weak_ptr和shared_ptr产生。
1、weak_ptr->weak_ptr:weak_ptr对weak_ptr:拷贝构造和拷贝赋值
__weak_count(const __weak_count& __r) noexcept : _M_pi(__r._M_pi) { if (_M_pi != nullptr) _M_pi->_M_weak_add_ref();}__weak_count& operator=(const __weak_count& __r) noexcept { _Sp_counted_base<_Lp> * __tmp = __r._M_pi; if (__tmp != nullptr) __tmp->_M_weak_add_ref(); if (_M_pi != nullptr) _M_pi->_M_weak_release(); _M_pi = __tmp; return *this;}
拷贝构造:入参的多态指针不为空,则共享的弱引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
原多态指针不为空就释放,然后交换相互的_M_pi这个多态指针
2、shared_ptr->weak_ptr:__weak_count(const __shared_count<_Lp> & __r) noexcept : _M_pi(__r._M_pi) { if (_M_pi != nullptr) _M_pi->_M_weak_add_ref();}__weak_count& operator=(const __shared_count<_Lp> & __r) noexcept { _Sp_counted_base<_Lp> * __tmp = __r._M_pi; if (__tmp != nullptr) __tmp->_M_weak_add_ref(); if (_M_pi != nullptr) _M_pi->_M_weak_release(); _M_pi = __tmp; return *this;}
拷贝构造:入参的多态指针不为空,则共享的弱引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
原多态指针不为空就释放,然后交换相互的_M_pi这个多态指针
long _M_get_use_count() const noexcept { return _M_pi != nullptr ? _M_pi->_M_get_use_count() : 0;}
weak_prt的计数,其实是调用多态指针_M_pi的虚函数
但是!
但是!
但是!,上面提到的多态指针_Sp_counted_base<_Lp> * __weak_count::_M_pi;,并没有new的地方,注定它的来源是shared_ptr,上述代码中的对其弱引用计数的修改,实际上是修改的shared_ptr的弱引用计数;对引用计数的获取,实际上是对shared_ptr的引用计数的获取。所以weak_ptr是的shared_ptr附属品。
void _M_swap(__shared_count& __r) noexcept { _Sp_counted_base<_Lp> * __tmp = __r._M_pi; __r._M_pi = _M_pi; _M_pi = __tmp;}
可以看到两个__shared_count的交换,实际上交换了相互的_M_pi这个多态指针,和上面__weak_count的交换一样,因为本就指向同一块内存,同一个(堆)对象。
__shared_count 1、raw ptr->shared_ptr由一颗原始指针去构造shared_ptr是一切的开始:
__shared_count(_Ptr __p) : _M_pi(0) { __try { _M_pi = new _Sp_counted_ptr<_Ptr, _Lp> (__p); } __catch(...) { delete __p; __throw_exception_again; }}
将原始指针用于堆对象_Sp_counted_ptr的构造,多态指针_M_pi将其引用,_Sp_counted_ptr从此开始维护引用计数和RAII
2、shared_ptr->shared_ptr__shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi) { if (_M_pi != 0) _M_pi->_M_add_ref_copy();}__shared_count& operator=(const __shared_count& __r) noexcept { _Sp_counted_base<_Lp> * __tmp = __r._M_pi; if (__tmp != _M_pi) { if (__tmp != 0) __tmp->_M_add_ref_copy(); if (_M_pi != 0) _M_pi->_M_release(); _M_pi = __tmp; } return *this;}
拷贝构造:入参的多态指针不为空,则共享的引用计数+1
拷贝赋值:入参的多态指针不为空,共享弱引用计数+1
原多态指针不为空就释放,然后交换相互的_M_pi这个多态指针
3、weak_ptr->shared_ptr__shared_count(const __weak_count& __r) : _M_pi(__r._M_pi) { if (_M_pi != nullptr) _M_pi->_M_add_ref_lock(); else __throw_bad_weak_ptr();}
如果弱引用计数中的多态指针_M_pi不为空,则引用计数+1
如果弱引用计数中的多态指针_M_pi为空,则抛出异常,即未引用weak_ptr如果不是生于某shared_ptr,将不能用作新的shared_ptr的创建,而什么时候使用weak_ptr去创建新的shared_ptr呢,怎么创建呢,一会儿分析。
long _M_get_use_count() const noexcept { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0;}bool _M_unique() const noexcept { return this->_M_get_use_count() == 1; }
_Sp_counted_base_Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) {}virtual ~_Sp_counted_base() noexcept {}virtual void _M_dispose() noexcept = 0;// Called when _M_weak_count drops to zero.virtual void _M_destroy() noexcept { delete this; }
可以看到抽象类_Sp_counted_base的构造函数,初始化了int类型的成员变量:_M_use_count ,_M_weak_count为1,即在原始指针构造shared_ptr时在堆上开辟内存,进行了初始化。
_M_destroy函数,用于销毁自己(实现类**_Sp_counted_ptr),而管理的资源的销毁放在了子类实现(_Sp_counted_ptr**)之中,可以推断,当功能项的子类堆对象析构时,或者析构前,会对管理的资源进行回收。
纯虚接口virtual void _M_dispose() noexcept = 0;在子类中的重写如下:
class _Sp_counted_ptr final : public _Sp_counted_base { public: explicit _Sp_counted_ptr(_Ptr __p) noexcept : _M_ptr(__p) {} virtual void _M_dispose() noexcept { delete _M_ptr; } virtual void _M_destroy() noexcept { delete this; } _Sp_counted_ptr(const _Sp_counted_ptr&) = delete; _Sp_counted_ptr& operator=(const _Sp_counted_ptr&) = delete; private: _Ptr _M_ptr;};
可以看到实现了_M_dispose(),其将资源真正释放。
而且,重写了_M_destroy(),将自身析构。
接下来就看引用计数加减逻辑和资源释放逻辑。
由于存在多线曾多共享引用计数进行访问,因此对于_M_use_count ,_M_weak_count都要进行原子操作:
void _M_add_ref_copy() { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }void _M_add_ref_lock();bool _M_add_ref_lock_nothrow();void _M_release() noexcept { // Be race-detector-friendly、 For more info see bits/c++config. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count); if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1) { _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count); _M_dispose(); // There must be a memory barrier between dispose() and destroy() // to ensure that the effects of dispose() are observed in the // thread that runs destroy(). // See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html if (_Mutex_base<_Lp> ::_S_need_barriers) { __atomic_thread_fence(__ATOMIC_ACQ_REL); } // Be race-detector-friendly、 For more info see bits/c++config. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count); if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1) { _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count); _M_destroy(); } }}void _M_weak_add_ref() noexcept { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1);}void _M_weak_release() noexcept { // Be race-detector-friendly、For more info see bits/c++config. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count); if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1) { _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count); if (_Mutex_base<_Lp> ::_S_need_barriers) { // See _M_release(), // destroy() must observe results of dispose() __atomic_thread_fence(__ATOMIC_ACQ_REL); } _M_destroy(); }}long _M_get_use_count() const noexcept { // No memory barrier is used here so there is no synchronization // with other threads. return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);}
void _M_add_ref_copy() 将引用计数+1
void _M_weak_add_ref()将弱引用计数+1
void _M_weak_release(),首先判断引用计数-1之前,即当前引用计数值是否为1,为1就代表没有别的shared_ptr在引用该资源,调用使用实现类重写的_M_destroy()将自己析构,做到了共享内存不泄露。
void _M_release(),首先判断引用计数-1之前,即当前引用计数值是否为1,为1就代表没有别的shared_ptr在引用该资源,于是调用使用实现类重写的_M_dispose()将资源释放!然后调用使用实现类重写的_M_destroy()将自己析构,做到了共享内存不泄露。
那么什么时候调用_M_weak_release(),什么时候调用_M_release()呢?并且为什么调用_M_weak_release()要去回收资源呢?
1、调用_M_weak_release()在weak_ptr对weak_ptr进行赋值时,左值将原本的弱引用计数-1时会调用。
在某个weak_ptr析构时,引用计数-1时会调用。
那么,是否存在共享内存中弱引用计数从1减到0的情况呢?答案是不存在:
1、当环境中不存在`weak_ptr`时,弱引用技术保持1。2、当环境中每次从`shared_ptr`中诞生一个`weak_ptr`时,弱引用技术+1,所以最终会递减到1,也不会存在0的情况。
因此weak_ptr不会引起管理的资源的回收。
2、调用_M_release()在shared_ptr对shared_ptr进行赋值时,左值将原本的弱引用计数-1时会调用。
在某个shared_ptr析构时,引用计数-1时会调用。
由于weak_ptr是由shared_ptr生出,并且也能使用weak_ptr去构造shared_ptr,那么势必能将weak_ptr提升为shared_ptr:
shared_ptr<_Tp> lock() const noexcept { return shared_ptr<_Tp> (*this, std::nothrow);}
这样做会增加引用计数,但是不增加弱引用技术,这样做的好处在于,当想要使用weak_ptr管理的资源时担心资源是否被释放,然后哦按段shared_ptr,如果为真,则资源未释放。
enable_shared_from_thistemplate
可以看到这个类,含有一个数据成员:_M_weak_this : __weak_ptr<_Tp>
__shared_ptr<_Tp, _Lp> shared_from_this() { return __shared_ptr<_Tp, _Lp> (this->_M_weak_this); }__weak_ptr<_Tp, _Lp> weak_from_this() noexcept { return this->_M_weak_this; }
其中shared_from_this()由内部weak_ptr产生一个shared_ptr,使得共享shared_ptr引用计数+1,那么其内部是如何被初始化的呢?从上面看来是通过调用:
template
再看一下构造shared_ptr时,入参为enable_shared_frome_this的构造函数:
template
在初次使用继承了enable_shared_from_this的堆对象取构造shared_ptr时,其内部回调用_M_enable_shared_from_this_with(__p)去接收这个原始指针。
然后在_M_enable_shared_from_this_with(__p)中通过友元函数__enable_shared_from_this_base返回一个enable_shared_from_this的指针,也就是将实际类型指针转型为父类指针。最后调用_M_weak_assign初始化其内weak_ptr。
此时共享的控制块中引用计数为1,弱引用计数也是1+1=2
所以当用户敲下以下代码的时候,就表示当前资源已经被一个weak_ptr引用了
可以推测,一个被shared_ptr管理的资源,通过std::enable_shared_from_this::shared_frome_this()来产生一个共享同一个资源控制快的新shared_ptr,从而保证在第一个shared_ptr声明结束的时候,由于还有shared_ptr管理,因此生命周期被延长了。比如:
struct B { auto asyncFunc(std::function