当前位置:   article > 正文

C++深度探索 - shared_ptr源码分析

shared_ptr源码

目录

源码版本        

shared_ptr

__shared_ptr

 __shared_count

_Sp_counted_base

_Sp_counted_ptr

总结

新版本构造流程

参考


源码版本        

        本篇文章分析是基于 gcc-4.9.0源代码版本进行分析,官方源码下载地址为:Index of /gnu/gcc

        github:jackvz/gcc: The GNU Compiler Collection... git clone -b releases/gcc-4.9.0 --single-branch git://gcc.gnu.org/git/gcc.git (github.com)


shared_ptr

        先看shared_ptr类,位于 libstdc++-v3\include\bits 

  1. template<typename _Tp>
  2. class shared_ptr : public __shared_ptr<_Tp>
  3. {
  4. public:
  5. /**
  6. * @brief Construct an empty %shared_ptr.
  7. * @post use_count()==0 && get()==0
  8. 默认构造就是调用基类 __shared_ptr的构造函数
  9. */
  10. constexpr shared_ptr() noexcept : __shared_ptr<_Tp>() { }
  11. shared_ptr(const shared_ptr&) noexcept = default;
  12. template<typename _Tp1>
  13. explicit shared_ptr(_Tp1* __p) : __shared_ptr<_Tp>(__p) { }
  14. template<typename _Tp1, typename _Deleter>
  15. shared_ptr(_Tp1* __p, _Deleter __d): __shared_ptr<_Tp>(__p, __d) { }

以上为类中小部分关键源代码:

  1. shared_ptr类中没有类成员变量。
  2. 该类继承于__shared_ptr基类(注意区分),构造函数调用了__shared_ptr的构造函数,将接管的普通指针传递给基类__shared_ptr
  3. 该类没有析构函数,从智能指针最终会自动释放内存的特性来看,释放工作肯定不是在该类进行。
     

__shared_ptr

        基类 __shared_ptr,源代码文件位于 libstdc++-v3\include\bits\shared_ptr_base.h

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

继续分析关键代码:

  1. 基类中有两个成员变量,_M_ptr 为智能指针接管的普通指针, _M_refcount 即引用计数器,类型为 __shared_count ,也就是说引用计数器类是单独实现的。
  2. 构造函数看,_M_ptr 获得了接管的普通指针的值,而_M_refcount 的构造函数也同样传入了这个普通指针。
  3. 重载了 *和 ->运算符,由上面的子类shared_ptr子类继承使用,使得智能指针最终能拥有和普通指针一样行为。
  4. 析构函数 = default,表明什么也没做,说明接管的普通指针也不是在这里释放的,所以有可能是由_M_refcount 所属类型 __shared_count计数器类来完成释放内存这个工作。

        简要描绘一下:


 __shared_count

        继续来看计数器的实现,__shared_count类位于 libstdc++-v3\include\bits\shared_ptr_base.h

  1. template<_Lock_policy _Lp>
  2. class __shared_count
  3. {
  4. public:
  5. constexpr __shared_count() noexcept : _M_pi(0) { }
  6. // 构造函数
  7. template<typename _Ptr>
  8. explicit __shared_count(_Ptr __p) : _M_pi(0)
  9. {
  10. __try
  11. {
  12. _M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p); //注意是用_Sp_counted_ptr类初始化
  13. }
  14. __catch(...)
  15. {
  16. delete __p;
  17. __throw_exception_again;
  18. }
  19. }
  20. //析构函数
  21. ~__shared_count() noexcept
  22. {
  23. if (_M_pi != nullptr)
  24. _M_pi->_M_release();
  25. }
  26. //拷贝构造
  27. __shared_count(const __shared_count& __r) noexcept : _M_pi(__r._M_pi)
  28. {
  29. if (_M_pi != 0)
  30. _M_pi->_M_add_ref_copy();
  31. }
  32. //赋值函数
  33. __shared_count& operator=(const __shared_count& __r) noexcept
  34. {
  35. _Sp_counted_base<_Lp>* __tmp = __r._M_pi;
  36. if (__tmp != _M_pi)
  37. {
  38. if (__tmp != 0)
  39. __tmp->_M_add_ref_copy();
  40. if (_M_pi != 0)
  41. _M_pi->_M_release();
  42. _M_pi = __tmp;
  43. }
  44. return *this;
  45. }
  46. private:
  47. friend class __weak_count<_Lp>;
  48. _Sp_counted_base<_Lp>* _M_pi;
  49. };

继续分析关键代码:

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

_Sp_counted_base

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

  1. template<_Lock_policy _Lp = __default_lock_policy>
  2. class _Sp_counted_base : public _Mutex_base<_Lp>
  3. {
  4. public:
  5. _Sp_counted_base() noexcept : _M_use_count(1), _M_weak_count(1) { }
  6. virtual ~_Sp_counted_base() noexcept { }
  7. // Called when _M_use_count drops to zero, to release the resources
  8. // managed by *this.
  9. virtual void _M_dispose() noexcept = 0;
  10. // Called when _M_weak_count drops to zero.
  11. virtual void _M_destroy() noexcept { delete this; }
  12. virtual void*
  13. _M_get_deleter(const std::type_info&) noexcept = 0;
  14. void _M_add_ref_copy()
  15. { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
  16. void _M_add_ref_lock();
  17. bool _M_add_ref_lock_nothrow();
  18. void _M_release() noexcept
  19. {
  20. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
  21. if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
  22. {
  23. _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
  24. _M_dispose();
  25. if (_Mutex_base<_Lp>::_S_need_barriers)
  26. {
  27. _GLIBCXX_READ_MEM_BARRIER;
  28. _GLIBCXX_WRITE_MEM_BARRIER;
  29. }
  30. // Be race-detector-friendly. For more info see bits/c++config.
  31. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
  32. if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,-1) == 1)
  33. {
  34. _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
  35. _M_destroy();
  36. }
  37. }
  38. }
  39. void _M_weak_add_ref() noexcept
  40. { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
  41. void _M_weak_release() noexcept
  42. {
  43. // Be race-detector-friendly. For more info see bits/c++config.
  44. _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
  45. if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
  46. {
  47. _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
  48. if (_Mutex_base<_Lp>::_S_need_barriers)
  49. {
  50. // See _M_release(),
  51. // destroy() must observe results of dispose()
  52. _GLIBCXX_READ_MEM_BARRIER;
  53. _GLIBCXX_WRITE_MEM_BARRIER;
  54. }
  55. _M_destroy();
  56. }
  57. }
  58. long _M_get_use_count() const noexcept
  59. {
  60. // No memory barrier is used here so there is no synchronization
  61. // with other threads.
  62. return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
  63. }
  64. private:
  65. _Sp_counted_base(_Sp_counted_base const&) = delete;
  66. _Sp_counted_base& operator=(_Sp_counted_base const&) = delete;
  67. _Atomic_word _M_use_count; // #shared
  68. _Atomic_word _M_weak_count; // #weak + (#shared != 0)
  69. };

继续分析:

  1. 可以看到该类定义了虚析构函数。
  2. 该类有两个类成员:_M_use_count(引用计数) 与 _M_weak_count(弱引用计数),对这两个数的操作需要具有原子性。这里需要注意,前者其实表达的是当前有多少个强指针在引用内部指针;后者表达的是当前 shared_count类型的使用次数,即如果该计数的值为0,则需要释放掉上面 __shared_ptr基类中的成员: _Sp_counted_base<_Lp>*  _M_pi;
  3. _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指针,这点和前面的猜测一致。
  4. _M_release函数可以看出,智能指针所接管的指针的释放内存工作只和_M_use_count有关,当_M_use_count减完时就会将其释放了,而_M_weak_count也是有作用的,他负责释放_Sp_counted_base本身,这也就是为什么weak_ptr可以保证智能指针这个对象有效,但不保证智能指针所引用的指针有效的原因了(这点和shared_ptr、weak_ptr的定义是完全一致的)。
  5. _M_add_ref_copy方法将引用计数_M_use_count加一,_M_weak_add_ref方法将弱引用计数_M_weak_count加一,这个自增过程是具有原子性的。

_Sp_counted_ptr

        _Sp_counted_ptr类是_Sp_counted_base的子类,位于libstdc++-v3\include\bits\shared_ptr_base.h:

  1. template<typename _Ptr, _Lock_policy _Lp>
  2. class _Sp_counted_ptr final : public _Sp_counted_base<_Lp>
  3. {
  4. public:
  5. explicit
  6. _Sp_counted_ptr(_Ptr __p) noexcept
  7. : _M_ptr(__p) { }
  8. virtual void
  9. _M_dispose() noexcept
  10. { delete _M_ptr; }
  11. virtual void
  12. _M_destroy() noexcept
  13. { delete this; }
  14. virtual void*
  15. _M_get_deleter(const std::type_info&) noexcept
  16. { return nullptr; }
  17. _Sp_counted_ptr(const _Sp_counted_ptr&) = delete;
  18. _Sp_counted_ptr& operator=(const _Sp_counted_ptr&) = delete;
  19. private:
  20. _Ptr _M_ptr;
  21. };

该类内容不多:

  1. 从代码中可以看到_Sp_counted_ptr_Sp_counted_base的派生类,并且__shared_count在初始化_M_pi时用的也是_Sp_counted_ptr
  2. _M_dispose方法的实现里确实删除了一开始shared_ptr接管的指针,_M_destroy方法用于释放自己的内存(由__shared_count调用)。


总结

        看完前面分析的内容再回过头来看,_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被析构都会使引用计数减一,减完就将智能指针持有的资源释放。

自己根据以上内容描绘的简要整体架构图:


新版本构造流程

        下面基于VS 2017编译器下,<memory>头文件下智能指针源码进行流程分析。

创建一个新的shared_ptr:

        当我们使用一个原始指针直接构造智能指针时:

shared_ptr<int>sptr( new int(3) );
  1. 初始化 shared_base类中的_Ty *_ptr 对象;
  2. 创建 shared_base类中的_Ref_count 对象;
  3. _Ref_count_base对象构造时,会分别为两个引用计数:  _Uses_count= 1 , _weaks_count= 1

        这里再次强调,_Uses_count 表达的是当前有多少个强指针在引用内部指针, _weaks_count表达的是当前 _Ref_count 对象类型的使用次数,即如果_weaks_count 计数的值为0,则会释放掉上面 _Ref_count 计数器对象。

        下面是引用计数增加的情况:

将另外一个shared_ptr赋值给新的shared_ptr:

        _Uses_count + 1 , _weaks_count不变。

将另外一个shared_ptr赋值给新的weak_ptr:

        _Uses_count 不变, _weaks_count +1 。

从weak_ptr获取一个shared ptr:

        _Uses_count + 1 , _weaks_count不变。

        引用计数减少的情况:

shared_ptr析构时:

        _Uses_count -1 。当 _Uses_count == 0时,则释放内部对象即 _Ty *_ptr  对象,并将_weaks_count  -1。

weak_ptr析构时:

        _weaks_count  -1, 当_weaks_count  == 0时,则释放自身_Ref_count*对象。


参考

      C++11的智能指针shared_ptr、weak_ptr源码解析_c++ 智能指针源码_彼 方的博客-CSDN博客

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号