首页 > 编程语言 >C++11中的智能指针shared_ptr、weak_ptr源码解析

C++11中的智能指针shared_ptr、weak_ptr源码解析

时间:2023-09-15 17:26:54浏览次数:46  
标签:11 __ weak count 源码 shared pi ptr

https://www.jb51.net/article/224028.htm −

目录

1、前言

本文仅对C++智能指针shared_ptr、weak_ptr源码进行解析,需要读者有一定的C++基础并且对智能指针有所了解,本文并不对智能指针的使用方法、使用场景、效率等方面进行阐述分析,这些知识需自行查阅相关书籍去了解

2、源码准备

本文是基于gcc-4.9.0的源代码进行分析,shared_ptr和weak_ptr是C++11才加入标准的,所以低版本的gcc源码是没有shared_ptr和weak_ptr的,建议选择4.9.0或更新的版本去学习,不同版本的gcc源码差异应该不小,但是原理和设计思想的一样的,下面给出源码下载地址
http://ftp.gnu.org/gnu/gcc

3、智能指针概念

智能指针(Smart pointers)是存储“指向动态分配(在堆上)的对象的指针”的对象。也就是说,智能指针其实是个对象。不过它的行为很像C++的内建指针,只是它们可以在适当的时候自动删除它们所指向的对象。智能指针在面对异常时有非常显著的作用,它们可以确保动态分配对象的完全析构。它们还可以用于跟踪多主人共享的动态分配对象。在概念上,智能指针可以看作拥有它所指向的对象,并因此在对象不再需要时负责将它删除。

4、源码解析

4.1、shared_ptr解析

4.1.1、shared_ptr

shared_ptr位于libstdc++-v3\include\bits\shared_ptr.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 template<typename _Tp> class shared_ptr : public __shared_ptr<_Tp> { public: ...     // 构造函数     template<typename _Tp1>     explicit shared_ptr(_Tp1* __p)         :__shared_ptr<_Tp>(__p)     {     } ... };
 

由于源代码过长,这里就只贴出其中一部分进行分析:

  1. 该类没有类成员
  2. 该类继承于__shared_ptr,构造函数也只是调用了__shared_ptr的构造函数而已,将接管的普通指针传递给__shared_ptr
  3. 该类没有重载*->运算符,从这点看shared_ptr似乎无法实现普通指针的功能,推测这两个运算符的重载是在父类__shared_ptr实现的
  4. 该类没有析构函数,从智能指针最终会自动释放内存的特性来看,释放工作肯定不是在该类进行了,接下来分析父类__shared_ptr的实现

4.1.2、__shared_ptr

__shared_ptr位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 template<typename _Tp, _Lock_policy _Lp> class __shared_ptr { public:     typedef _Tp element_type; ...     // 构造函数     template<typename _Tp1>     explicit __shared_ptr(_Tp1* __p)         :_M_ptr(__p), _M_refcount(__p)     {         __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)         static_assert( !is_void<_Tp>::value, "incomplete type" );         static_assert( sizeof(_Tp1) > 0, "incomplete type" );         __enable_shared_from_this_helper(_M_refcount, __p, __p);     }       // 析构函数     ~__shared_ptr() = default;       typename std::add_lvalue_reference<_Tp>::type     operator*() const noexcept     {         _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);         return *_M_ptr;     }       _Tp*     operator->() const noexcept     {         _GLIBCXX_DEBUG_ASSERT(_M_ptr != 0);         return _M_ptr;     } ...   private:     _Tp* _M_ptr;    // Contained pointer.     __shared_count<_Lp> _M_refcount;    // Reference counter. };
 

同样的,源代码比较长且不是分析的重点,所以只贴出一部分进行分析:

可以看到里面有两个类成员:_M_ptr(由智能指针接管的普通指针)、_M_refcount(引用计数器,类型为__shared_count)

  1. 从构造函数看,_M_ptr获得了接管的普通指针的值,而_M_refcount的构造也同样需要这个值
  2. 重载了*->运算符,由shared_ptr继承使用,使得智能指针最终能拥有和普通指针一样行为,尽管智能指针本质上是一个对象
  3. 从析构函数来看,里面啥也没做,说明接管的普通指针也不是在这里释放的,所以有可能是由_M_refcount来完成释放内存这个工作,下面分析__shared_count的实现

4.1.3、__shared_count

__shared_count位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 template<_Lock_policy _Lp> class __shared_count { public:     constexpr __shared_count() noexcept : _M_pi(0)     {     }       template<typename _Ptr>     explicit __shared_count(_Ptr __p) : _M_pi(0)     {         __try         {             _M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p);         }         __catch(...)         {             delete __p;             __throw_exception_again;         }     }       template<typename _Ptr, typename _Deleter>     __shared_count(_Ptr __p, _Deleter __d)         :__shared_count(__p, std::move(__d), allocator<void>())     {     }       template<typename _Ptr, typename _Deleter, typename _Alloc>     __shared_count(_Ptr __p, _Deleter __d, _Alloc __a)         :_M_pi(0)     {         typedef _Sp_counted_deleter<_Ptr, _Deleter, _Alloc, _Lp> _Sp_cd_type;         typedef typename allocator_traits<_Alloc>::template rebind_traits<_Sp_cd_type> _Alloc_traits;         typename _Alloc_traits::allocator_type __a2(__a);         _Sp_cd_type* __mem = 0;         __try         {             __mem = _Alloc_traits::allocate(__a2, 1);             _Alloc_traits::construct(__a2, __mem, __p, std::move(__d), std::move(__a));             _M_pi = __mem;         }         __catch(...)         {             __d(__p); // Call _Deleter on __p.             if (__mem)                 _Alloc_traits::deallocate(__a2, __mem, 1);             __throw_exception_again;         }     }       template<typename _Tp, typename _Alloc, typename... _Args>     __shared_count(_Sp_make_shared_tag, _Tp*, const _Alloc& __a, _Args&&... __args)         :_M_pi(0)     {         typedef _Sp_counted_ptr_inplace<_Tp, _Alloc, _Lp> _Sp_cp_type;         typedef typename allocator_traits<_Alloc>::template rebind_traits<_Sp_cp_type> _Alloc_traits;         typename _Alloc_traits::allocator_type __a2(__a);         _Sp_cp_type* __mem = _Alloc_traits::allocate(__a2, 1);         __try         {             _Alloc_traits::construct(__a2, __mem, std::move(__a),             std::forward<_Args>(__args)...);             _M_pi = __mem;         }         __catch(...)         {             _Alloc_traits::deallocate(__a2, __mem, 1);             __throw_exception_again;         }     }       template<typename _Tp, typename _Del>     explicit __shared_count(std::unique_ptr<_Tp, _Del>&& __r)         :_M_pi(0)     {         using _Ptr = typename unique_ptr<_Tp, _Del>::pointer;         using _Del2 = typename conditional<is_reference<_Del>::value, reference_wrapper<typename remove_reference<_Del>::type>, _Del>::type;         using _Sp_cd_type = _Sp_counted_deleter<_Ptr, _Del2, allocator<void>, _Lp>;         using _Alloc = allocator<_Sp_cd_type>;         using _Alloc_traits = allocator_traits<_Alloc>;         _Alloc __a;         _Sp_cd_type* __mem = _Alloc_traits::allocate(__a, 1);         _Alloc_traits::construct(__a, __mem, __r.release(), __r.get_deleter());  // non-throwing         _M_pi = __mem;     }       explicit __shared_count(const __weak_count<_Lp>& __r);       explicit __shared_count(const __weak_count<_Lp>& __r, std::nothrow_t);       ~__shared_count() noexcept     {         if (_M_pi != nullptr)             _M_pi->_M_release();     }       __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;     }       void _M_swap(__shared_count& __r) noexcept     {         _Sp_counted_base<_Lp>* __tmp = __r._M_pi;         __r._M_pi = _M_pi;         _M_pi = __tmp;     }       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; }       void* _M_get_deleter(const std::type_info& __ti) const noexcept     { return _M_pi ? _M_pi->_M_get_deleter(__ti) : nullptr; }       bool _M_less(const __shared_count& __rhs) const noexcept     { return std::less<_Sp_counted_base<_Lp>*>()(this->_M_pi, __rhs._M_pi); }       bool _M_less(const __weak_count<_Lp>& __rhs) const noexcept     { return std::less<_Sp_counted_base<_Lp>*>()(this->_M_pi, __rhs._M_pi); }       friend inline bool operator==(const __shared_count& __a, const __shared_count& __b) noexcept     { return __a._M_pi == __b._M_pi; }   private:     friend class __weak_count<_Lp>;     _Sp_counted_base<_Lp>* _M_pi; }
 

从源代码可以获得以下几点信息:

有一个类成员:_M_pi(计数器,类型为_Sp_counted_base)

  1. 只有构造函数为_M_pi分配了内存,并且该类并没有直接持有从前面一直传递过来的那个普通指针,而是继续将其传递给_M_pi,所以内存的释放也不是直接在该类进行的。
  2. 拷贝构造函数没有分配内容,而是把拷贝对象的_M_pi直接拿过来了,有点类似于浅拷贝的意思,然后调用了_M_pi_M_add_ref_copy方法(后面会讲),增加了一次引用计数。赋值函数也是同样的道理,但是由于赋值函数的特殊性(当赋值对象原先就存在时调用赋值函数,否则调用拷贝构造函数),要先调用_M_pi_M_release方法(后面会讲)将自己持有的内存释放掉,其余操作和拷贝构造函数是一样的
  3. 从析构函数中可以看到,里面并没有直接释放掉为_M_pi分配的内存,而是调用了_M_pi_M_release方法,可以大概猜测是通过_M_release方法释放了_M_pi的内存(delete this指针,后面会讲)
  4. 由于__shared_count里面的方法都是借助_M_pi实现的,并且到这里都还没有见到释放那个普通指针的代码,所以还是得继续看_M_pi究竟做了什么工作,接下来继续看_Sp_counted_base的实现

4.1.4、_Sp_counted_base

_Sp_counted_base位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 template<_Lock_policy _Lp = __default_lock_policy> class _Sp_counted_base : public _Mutex_base<_Lp> { public:     _Sp_counted_base() noexcept         : _M_use_count(1), _M_weak_count(1)     {     }         virtual ~_Sp_counted_base() noexcept     {     }       virtual void _M_dispose() noexcept = 0;         virtual void _M_destroy() noexcept     { delete this; }       virtual void* _M_get_deleter(const std::type_info&) noexcept = 0;       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     {         _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();             if (_Mutex_base<_Lp>::_S_need_barriers)             {                 _GLIBCXX_READ_MEM_BARRIER;                 _GLIBCXX_WRITE_MEM_BARRIER;             }               _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     {         _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)             {                 _GLIBCXX_READ_MEM_BARRIER;                 _GLIBCXX_WRITE_MEM_BARRIER;             }             _M_destroy();         }     }       long _M_get_use_count() const noexcept     {         return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);     }   private:     _Sp_counted_base(_Sp_counted_base const&) = delete;     _Sp_counted_base& operator=(_Sp_counted_base const&) = delete;       _Atomic_word  _M_use_count;     // #shared     _Atomic_word  _M_weak_count;    // #weak + (#shared != 0) };
 

从源代码可以获得以下几点信息:

  1. 有两个类成员:_M_use_count(引用计数)、_M_weak_count(弱引用计数),对这两个数的操作需要具有原子性
  2. _M_release方法是该类的关键,可以看到先将_M_use_count自减1,然后判断自减前_M_use_count的值是否为1(无其他人引用),如果为1,则调用_M_dispose方法(虚函数,由派生类实现,估计是释放前面一直说的那个由智能指针接管的普通指针)。接下来将_M_weak_count自减1,然后判断自减前_M_weak_count的值是否为1(无其他人引用),如果为1,则调用_M_destroy方法,而_M_destroy方法里面释放了this指针,这点和前面的猜测一致
  3. _M_release可以看出,智能指针所接管的指针的释放内存工作只和_M_use_count有关,当_M_use_count减完时就会将其释放了,而_M_weak_count也是有作用的,他负责释放_Sp_counted_base本身,这也就是为什么weak_ptr可以保证智能指针这个对象有效,但不保证智能指针所引用的指针有效的原因了(这点和shared_ptr、weak_ptr的定义是完全一致的)
  4. 其他的方法就很简单了,比如_M_add_ref_copy方法将引用计数_M_use_count加一,_M_weak_add_ref方法将弱引用计数_M_weak_count加一,这个自增过程是具有原子性的,这里就不赘述了,大家可以自行看一下具体实现

4.1.5、_Sp_counted_ptr

_Sp_counted_ptr位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template<typename _Ptr, _Lock_policy _Lp> class _Sp_counted_ptr final : public _Sp_counted_base<_Lp> { 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; }       virtual void* _M_get_deleter(const std::type_info&) noexcept     { return nullptr; }       _Sp_counted_ptr(const _Sp_counted_ptr&) = delete;     _Sp_counted_ptr& operator=(const _Sp_counted_ptr&) = delete;   private:     _Ptr _M_ptr; };
 
  1. 从源代码中可以看到_Sp_counted_ptr_Sp_counted_base的派生类,并且__shared_count在初始化_M_pi时用的也是_Sp_counted_ptr
  2. 接着看_M_dispose方法的实现,里面确实删除了一开始shared_ptr接管的指针,_M_destroy方法用于释放自己的内存(由__shared_count调用),和前面猜想一致

4.1.6、shared_ptr总结

看完前面分析的内容再回过头来看,_Sp_counted_base_M_add_ref_copy方法是整个流程的关键,它实现了引用计数器的增加,那么在何时调用它就是关键了。通过在代码中检索,可以查到__shared_count的赋值构造函数和拷贝构造函数调用了它(其实也只有可能是这里啦,因为只有它的类成员有_Sp_counted_base),这样整个流程也就解释通了:

  1. __shared_count的成员_M_pi只会初始化一次(构造函数中分配内存初始化的)
  2. 后面调用拷贝构造时(这个行为由__shared_ptr触发,__shared_ptr的拷贝构造函数和赋值函数都会调用__shared_count的拷贝构造函数),__shared_count只是简单复制了_M_pi而已,并没有重新分配内存,然后再调用_M_add_ref_copy增加一次引用计数,这样就实现了shared_ptr每多一份拷贝就增加一次引用计数的特性了
  3. 每一个__shared_count被析构都会使引用计数减一,减完就将智能指针持有的资源释放,这个前面已经分析过了,这里就不赘述了

4.2、weak_ptr解析

4.2.1、weak_ptr

weak_ptr位于libstdc++-v3\include\bits\shared_ptr.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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);     } }
 

从源代码中可以看出以下几点:

  1. 该类没有类成员
  2. 从构造函数的参数来看(无参构造函数除外),只能使用shared_ptrweak_ptr来构造一个weak_ptr对象,包括赋值函数也是这样的,这就和shared_ptr有很大区别了,从4.1.1小节可以看到shared_ptr是可以使用普通指针来构造的
  3. 可以调用lock方法来获得一个shared_ptrlock方法的实现后面再讲

该类没有重载*->运算符,接下来分析其父类__weak_ptr的实现

4.2.2、__weak_ptr

__weak_ptr位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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>;       _Tp* _M_ptr;         // Contained pointer.     __weak_count<_Lp> _M_refcount;    // Reference counter. }
 

从源代码中可以看出以下几点信息:

  1. 有两个类成员:_M_ptr(由智能指针接管的普通指针)、_M_refcount(弱引用计数器,类型为__weak_count)
  2. 从构造函数看,_M_ptr获得了接管的普通指针的值,而_M_refcount的构造并不需要这个值了(这点和__shared_ptr不一样了),_M_refcount只能借助其他__shared_ptr_M_refcount或者__weak_ptr_M_refcount来进行构造(注意这两个的_M_refcount类型不同,说明__weak_count支持多种类型进行构造)
  3. 拷贝构造函数和赋值函数的实现同上
  4. 该类依然没有重载*->运算符,由于接下去已无继承关系,所以weak_ptr不具备普通指针的特性,无法直接使用资源,这点符合weak_ptr的定义
  5. 既然weak_ptr无法直接使用资源,那他设计_M_ptr这个成员的意图在哪里呢?答案就是lock方法将weak_ptr转换为shared_ptr时是需要将这个指针传递过去的,不然连接管的指针都没了转换的意义也就没了
  6. 析构函数啥也没做,因为weak_ptr不持有资源,不对资源的释放产生影响,接下来对__weak_count进行分析

4.2.3、__weak_count

__weak_count的实现位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 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(const __weak_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();     }       __weak_count<_Lp>&     operator=(const __shared_count<_Lp>& __r) noexcept     {         _Sp_counted_base<_Lp>* __tmp = __r._M_pi;         if (__tmp != 0)             __tmp->_M_weak_add_ref();         if (_M_pi != 0)             _M_pi->_M_weak_release();         _M_pi = __tmp;         return *this;     }       __weak_count<_Lp>& operator=(const __weak_count<_Lp>& __r) noexcept     {         _Sp_counted_base<_Lp>* __tmp = __r._M_pi;         if (__tmp != 0)             __tmp->_M_weak_add_ref();         if (_M_pi != 0)             _M_pi->_M_weak_release();         _M_pi = __tmp;         return *this;     }       void _M_swap(__weak_count<_Lp>& __r) noexcept     {         _Sp_counted_base<_Lp>* __tmp = __r._M_pi;         __r._M_pi = _M_pi;         _M_pi = __tmp;     }       long _M_get_use_count() const noexcept     { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }       bool _M_less(const __weak_count& __rhs) const noexcept     { return std::less<_Sp_counted_base<_Lp>*>()(this->_M_pi, __rhs._M_pi); }       bool _M_less(const __shared_count<_Lp>& __rhs) const noexcept     { return std::less<_Sp_counted_base<_Lp>*>()(this->_M_pi, __rhs._M_pi); }       friend inline bool operator==(const __weak_count& __a, const __weak_count& __b) noexcept     { return __a._M_pi == __b._M_pi; }   private:     friend class __shared_count<_Lp>;     _Sp_counted_base<_Lp>*  _M_pi; }
 

从源代码可以获得以下几点信息:

  1. 有一个类成员:_M_pi(计数器,类型为_Sp_counted_base)
  2. 仔细一看__shared_count里也持有这个成员,类型一模一样,这样也就解释得通为什么__shared_count__weak_count可以互相转换了,转换的方式很简单:
__shared_count转换为__weak_count的过程为:
拷贝_M_pi,然后调用_M_weak_add_ref方法增加一次弱引用计数__weak_count转换为__shared_count的过程为:
拷贝_M_pi,然后调用_M_add_ref_copy方法增加一次引用计数
  1. 构造函数、拷贝构造函数、赋值函数均不为_M_pi分配了内存,这点也可以看出weak_ptr确实是shared_ptr的附属品而已,自己不持有资源不控制资源
  2. 析构函数中调用了_M_pi_M_weak_release方法,释放了_M_pi的内存(条件满足的情况下才会释放)
  3. 接下来的内容和3.1.4小节还有3.1.5小节的内容是一样的,这里就不赘述

4.2.4、回过头看weak_ptr中lock方法的实现

weak_ptrlock方法调用了shared_ptr的构造函数如下:

?
1 2 3 4 shared_ptr(const weak_ptr<_Tp>& __r, std::nothrow_t)     :__shared_ptr<_Tp>(__r, std::nothrow) { }
 

从上面的代码可以看出调用了__shared_ptr的构造函数,代码如下:

?
1 2 3 4 5 __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_M_refcount成员(类型为__weak_count)来构造__shared_ptr_M_refcount成员(类型为__shared_count),然后再判断引用计数器是否为0,为零的话就将__shared_ptr_M_ptr成员置为nullptr,即lock函数执行失败;不为零的话就会正常构建一个shared_ptr了。

上面讲的构造_M_refcount的方法如下所示:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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使用__weak_count_M_pi来构建自己的_M_pi,从前面的分析我们可以知道,在所有的shared_ptrweak_ptr消亡之前,_M_pi的内存是不会被释放的,所以这里就算之前的shared_ptr已经全部消亡(即资源已释放),_M_pi还是有效的(因为weak_ptr还没有消亡)。而通过判断_M_add_ref_lock_nothrow的返回值来确定是否要将_M_pi置为nullptr,可以看到判断的条件为_M_use_count是否为0(即判断资源是否被释放了)。

接下来再看一下__shared_count_M_get_use_count方法,代码如下:

?
1 2 long _M_get_use_count() const noexcept { return _M_pi != 0 ? _M_pi->_M_get_use_count() : 0; }
 

代码比较简单,意思就是如果此时资源已经被释放了(对应_M_pi值为nullptr),则会返回0,再回到上面第2点讲的那里,_M_ptr将被设置为nullptr,即资源无效,lock函数执行失败。
至此weak_ptrlock方法的实现原理就全部讲解完毕。

4.3、enable_shared_from_this解析

4.3.1、从一个典型的例子来认识智能指针的不足之处

有时候我们需要在一个被shared_ptr管理的对象的内部获取自己的shared_ptr,比如下面这个的例子:

?
1 2 3 4 5 6 7 8 9 10 11 12 class Ptr { public:     void fun()     {         std::shared_ptr<Ptr> p(this);         std::cout << sp->use_count() << std::endl;     } };   std::shared_ptr<Ptr> p= std::make_shared<Ptr>(); p->fun(); //输出为1
 

从上面这个简单的例子可以看到,fun输出的居然是1而不是2,这是为什么?倒回去4.1.2小节可以看到,当使用普通指针(上面的那个this)去构造shared_ptr时,构造出来的shared_ptr一定是独立的,不与其他人共享的。这样就会出现一个非常严重的问题,那就是析构时会导致对象被重复释放, 从而引发错误

4.3.2、改进方法

现在明确一下我们的需求:在一个对象内部构造该对象的shared_ptr 时,即使该对象已经被shared_ptr管理着,也不会造成对象被两个独立的智能指针管理。这就要求我们在对象内构造对象的智能指针时,必须能识别有对象是否已经由其他智能指针管理,智能指针的数量,并且我们创建智能指针后也能让之前的智能指针感知到。当然标准已经也给出了解决了这个问题办法,那就是使用接下来所提到的enable_shared_from_this

4.3.3、enable_shared_from_this解析

enable_shared_from_this的实现位于libstdc++-v3\include\bits\shared_ptr.h

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 template<typename _Tp> class enable_shared_from_this { protected:     constexpr enable_shared_from_this() noexcept { }       enable_shared_from_this(const enable_shared_from_this&) noexcept { }       enable_shared_from_this& operator=(const enable_shared_from_this&) noexcept     { return *this; }       ~enable_shared_from_this() { }   public:     shared_ptr<_Tp> shared_from_this()     { return shared_ptr<_Tp>(this->_M_weak_this); }       shared_ptr<const _Tp> shared_from_this() const     { return shared_ptr<const _Tp>(this->_M_weak_this); }   private:     template<typename _Tp1>     void _M_weak_assign(_Tp1* __p, const __shared_count<>& __n) const noexcept     { _M_weak_this._M_assign(__p, __n); }       template<typename _Tp1>     friend void __enable_shared_from_this_helper(const __shared_count<>& __pn, const enable_shared_from_this* __pe, const _Tp1* __px) noexcept     {         if (__pe != 0)             __pe->_M_weak_assign(const_cast<_Tp1*>(__px), __pn);     }       mutable weak_ptr<_Tp>  _M_weak_this; };
 

从源代码可以获得以下几点信息:

  1. 有一个类成员:_M_weak_this
  2. 该类需要被继承,被需要用智能指针管理的对象继承
  3. 我们平时就是使用该类的shared_from_this方法的,可以看到其实现就是利用_M_weak_this构造一个shared_ptr对象而已
  4. 该类并没有直接初始化_M_weak_this,而是提供了_M_weak_assign方法来构造_M_weak_this,其实现比较简单,就是调用了weak_ptr_M_assign方法
  5. 那么问题来了,_M_weak_assign方法由谁调用呢?从后面我们可以知道是由一个全局函数__enable_shared_from_this_helper调用的,该函数有一种重载形式是enable_shared_from_this的友元函数,从上面的代码中就可以看到了,那唯一一个友元函数就是__enable_shared_from_this_helper,里面调用了enable_shared_from_this_M_weak_assign方法。
  6. __enable_shared_from_this_helper函数要在哪个时间点使用才能达到预期的效果呢?答案当然是在__shared_ptr的构造函数中调用。下面列出了__shared_ptr部分构造函数,可以看到确实调用了__enable_shared_from_this_helper,证实了前面的猜想
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 template<typename _Tp, _Lock_policy _Lp> class __shared_ptr { public: ...       template<typename _Tp1>     explicit __shared_ptr(_Tp1* __p)         :_M_ptr(__p)         ,_M_refcount(__p)     {         __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)         static_assert( !is_void<_Tp>::value, "incomplete type" );         static_assert( sizeof(_Tp1) > 0, "incomplete type" );         __enable_shared_from_this_helper(_M_refcount, __p, __p);     }       template<typename _Tp1, typename _Deleter>     __shared_ptr(_Tp1* __p, _Deleter __d)         :_M_ptr(__p)         ,_M_refcount(__p, __d)     {         __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)         __enable_shared_from_this_helper(_M_refcount, __p, __p);     }       template<typename _Tp1, typename _Deleter, typename _Alloc>     __shared_ptr(_Tp1* __p, _Deleter __d, _Alloc __a)         :_M_ptr(__p)         ,_M_refcount(__p, __d, std::move(__a))     {         __glibcxx_function_requires(_ConvertibleConcept<_Tp1*, _Tp*>)         __enable_shared_from_this_helper(_M_refcount, __p, __p);     }   ... };
 

4.3.4、__enable_shared_from_this_helper解析

__enable_shared_from_this_helper的实现位于libstdc++-v3\include\bits\shared_ptr_base.h

?
1 2 3 4 5 6 7 // Friend of enable_shared_from_this. template<typename _Tp1, typename _Tp2> void __enable_shared_from_this_helper(const __shared_count<>&, const enable_shared_from_this<_Tp1>*, const _Tp2*) noexcept;   template<_Lock_policy _Lp> inline void __enable_shared_from_this_helper(const __shared_count<_Lp>&, ...) noexcept { }
 

这里有必要再看一下__enable_shared_from_this_helper函数的实现,有两种形式,第一种就是上面提到过的那个enable_shared_from_this的友元函数,而第二种重载形式里面啥都没有干。为什么需要重载这两个函数呢?答案很简单,当我们一个类继承了enable_shared_from_this之后,这个类肯定可以转换为enable_shared_from_this类型了,此时在__shared_ptr中调用的__enable_shared_from_this_helper就是上面第一种情况了,这种情况下就可以使用shared_from_this函数了;反之,当类没有继承enable_shared_from_this时,就是调用第二中形式的__enable_shared_from_this_helper,此时也就不能使用shared_from_this函数了。
至此,为什么在使用shared_from_this前,对应的类需要继承enable_shared_from_this的原因也就全部揭晓了。

5、总结

本文先是简单介绍了C++智能指针的定义,然后通过对源码进行详细分析,我们了解了shared_ptrweak_ptr以及enable_shared_from_this的实现原理。源代码内容并不是很复杂,没有用到什么很高深的语法糖,但是阅读起来非常绕(因为这三个类的关联错综复杂),这就需要我们有耐心地一步一步去深入学习。

最后,如果大家觉得本文写得好的话麻烦点赞收藏关注一下谢谢,也可以关注该专栏,以后会有更多优质文章输出的。

您可能感兴趣的文章:

标签:11,__,weak,count,源码,shared,pi,ptr
From: https://www.cnblogs.com/tomato-haha/p/17705504.html

相关文章

  • Avalonia 11中动态变更主题和颜色的方法
       首先添加一个这个工具类:usingAvalonia.Controls.ApplicationLifetimes;usingAvalonia.Styling;usingAvalonia.Themes.Fluent;usingAvalonia.Themes.Simple;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSyst......
  • 《Python从入门到实战》-源码篇-Scrapy
    什么是Scrapy?Python写的开源爬虫框架,快速、简单的方式构建爬虫,从网站上提取你所需要的数据。优点:功能非常强大的爬虫框架,不仅能便捷地构建request,还有强大的selector能够方便地解析response,最受欢迎的还是它的性能,既抓取和解析的速度,它的downloader是多线程的,request是异步......
  • Qemu源码分析(2)—Apple的学习笔记
    一,前言最近从main开始看了opt参数相关的解析,这个比较简单我就不写了,然后当时我搞不清楚的是MachineClass和TypeImpl类的关系。本节主要分析的其实就是分析machine_class怎么来的,其实也就是machine_class=select_machine();二,源码分析关于mc的来历type_initialize中ti->class->ty......
  • 用源码创建虚拟资源加密平台,扫码支付获取资源
    大家都知道,八图片是一个可以对图片或网址进行二维码加密的网络平台,很多人利用这个平台的加密功能,自动化的出售虚拟产品。你可以将任何资源或产品隐藏在加密地址的后面,让用户扫码支付后获取。这个网站不仅提供了加密的功能,还提供了一套简化版的源码,可以利用这个源码,搭建一个一模一......
  • 每日一练:无感刷新页面(附可运行的前后端源码,前端vue,后端node)
    1、前言想象下,你正常在网页上浏览页面。突然弹出一个窗口,告诉你登录失效,跳回了登录页面,让你重新登录。你是不是很恼火。这时候无感刷新的作用就体现出来了。2、方案2.1redis设置过期时间在最新的技术当中,token一般都是在Redis服务器存着,设置过期时间。只要在有效时间内,重新发......
  • win11显示星期
    点击"开始"菜单,在搜索框输入"区域"点击"其他设置"--"日期"在短日期后面加上"ddd"或者"dddd"注意要和前面的日期用空格、/、-等字符分开ddd代表周一dddd代表星期一为了尽可能短,我把年去掉了,用的是ddd的周格式......
  • window11恢复右键展示完整菜单
    //终端->右键管理员身份运行regadd"HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32"/f/ve(附[恢复window11右键模式]:regdelete"HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32......
  • 部署 kn v1.11.0
    下载kn#wgethttps://github.com/knative/client/releases/download/knative-v1.11.0/kn-linux-amd64部署kn#mvkn-linux-amd64/usr/local/bin/kn#chmod+x/usr/local/bin/kn验证kn#knversionVersion:v1.11.0BuildDate:2023-07-2707:42:56GitRevis......
  • 在线直播源码,AlarmManager定时器设置
    在线直播源码,AlarmManager定时器设置Android开发中一般常见的定时器有Timer、Handler。某些场景下也会使用到AlarmManager,相对于前面两者,AlarmManager功能更加多样,某些场景下有更准确的定时效果。 //Timer    Timertimer=newTimer();    TimerTaskt......
  • Spring源码分析(三)自动注入与精确注入
    上篇文章讲了1.4小节中关于依赖注入和方法注入的内容。这篇文章继续这节中的其他内容,顺便解决下上篇文章中留下的问题---注入模型前言:在看下面的内容之前,我们先对自动注入和精确注入有一个大概的了解,所谓精确注入就是指通过构造函数或setter方法指定了我们对象之间的依赖,也就是......