shared_ptr源码分析

shared_ptr是从C++11开始提供的智能指针,用于管理多个指针对同一实体的资源释放问题,保证在多个指针指向同一实体时,仅最后一个指针解除指向时才会释放资源。

shared_ptr构造

shared_ptr<_Tp>继承自__shared_ptr<_Tp>,其功能主要由其实现。让我们以单值构造函数为切入点开始分析。

      template<typename _Yp, typename = _Constructible<_Yp*>>
	explicit
	shared_ptr(_Yp* __p) : __shared_ptr<_Tp>(__p) { }

从中我们可以看到,调用了基类的构造函数,定义如下

      template<typename _Yp, typename = _SafeConv<_Yp>>
	explicit
	__shared_ptr(_Yp* __p)
	: _M_ptr(__p), _M_refcount(__p, typename is_array<_Tp>::type())
	{
	  static_assert( !is_void<_Yp>::value, "incomplete type" );
	  static_assert( sizeof(_Yp) > 0, "incomplete type" );
	  _M_enable_shared_from_this_with(__p);
	}

其初始化了成员变量_M_ptr_M_refcount,这两个成员变量的定义如下。element_type就是构造时传入的指针指向类型,这里我们可以看到,share_ptr的实现就是最基本的指针加上引用计数。

element_type*	   _M_ptr;         // Contained pointer.
__shared_count<_Lp>  _M_refcount;    // Reference counter.

让我们接下看看_M_refcount的构造过程,其调用如下构造函数,我们能够看到他初始化了数据成员_M_pi

      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;
	    }
	}

数据成员_M_pi的定义如下,他是一个基类的指针,可以指向任何派生类,所以_M_pi = new _Sp_counted_ptr<_Ptr, _Lp>(__p)并不存在任何语法问题。

_Sp_counted_base<_Lp>*  _M_pi;

让我们接着查看代码,首先是_Sp_counted_ptr<_Ptr, _Lp>(__p)的构造,其初始化了数据成员_Ptr _M_ptr,这里我们可以发现_Sp_counted_ptr存在一个指针指向其引用的对象。

explicit
_Sp_counted_ptr(_Ptr __p) noexcept
: _M_ptr(__p) { }

从上面的代码中我们可以看出,其没有显式调用基类构造函数,所以使用基类默认构造函数,其默认构造函数如下:

_Sp_counted_base() noexcept
: _M_use_count(1), _M_weak_count(1) { }

其初始化数据成员_M_use_count_M_weak_count_M_use_count表示的为共有多少share_ptr指向内存实体,当_M_use_count == 0时便会释放内存所指向的实体。之后的析构分析我们便会看到其如何使用,_M_weak_count是供weak_ptr使用,保证weak_ptr能够感知其实体是否依然存在。

      _Atomic_word  _M_use_count;     // #shared
      _Atomic_word  _M_weak_count;    // #weak + (#shared != 0)
赋值分析

赋值包括两个主要操作:copy construc和operator=。查看相应的函数,我们可以看到他们的定义如下:

shared_ptr& operator=(const shared_ptr&) noexcept = default;
shared_ptr(const shared_ptr&) noexcept = default;

__shared_ptr(const __shared_ptr&) noexcept = default;
__shared_ptr& operator=(const __shared_ptr&) noexcept = default;

基类和子类的copy construc和operator=都使用默认方法,也就是说其在调用时会分别调用成员变量的copy construc和operator=。派生类没有数据成员,而基类__shared_ptr包含成员变量。

 element_type*	   _M_ptr;         // Contained pointer.
 __shared_count<_Lp>  _M_refcount;    // Reference counter.

_M_ptr为最基本的指针,所以每次都会进行重新赋值。而__shared_count<_Lp>为自定义class,我们继续跟踪其行为。

      __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;
      }

在进行复制构造时,我们可以看到其令this->_M_pi = other._M_pi,这里_M_pi的定义为_Sp_counted_base<_Lp>* _M_pi。他是一个指针,因此在复制构造时,此结构直接指向被复制对象的实体,保证了多个share_ptr只有一个引用计数实体。operator=操作不同的是会先取消之前的引用计数(如果计数归零还需要释放空间),再添加新的计数。

      
      void
      _M_add_ref_copy()
      { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }

      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();
              }
	  }
      }

上述两个便是_Sp_counted_base_M_add_ref_copy()M_release(),对于_M_add_ref_copy()其就是_M_use_count + 1。而对于M_release(),在析构时再详细介绍。

析构分析

shared_ptr超出其definite scope时,需要智能释放对应内存,即当其为最后一个引用时需要释放内存,而如果还存在其他引用则不进行释放。
shared_ptr__shared_ptr都没有定义析构函数,所以我们直接查看__shared_count<_Lp> _M_refcount数据成员的析构函数。其定义如下

      ~__shared_count() noexcept
      {
	if (_M_pi != nullptr)
	  _M_pi->_M_release();
      }

依旧是调用_M_pi->_M_release(),现在让我们来看看其具体过程如何。

      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();
              }
	  }
      }

其先对_M_use_count -- == 1进行判断,注意这里判断的是减之前的值,如果值为1表示这是最后一个引用的shared_ptr,此时会调用_M_dispose()函数。

	  // Called when _M_use_count drops to zero, to release the resources
      // managed by *this.
      virtual void
      _M_dispose() noexcept = 0;

其为虚函数,其实现由派生类实现,让我们查看其中一个派生类 _Sp_counted_ptr<_Ptr, _Lp>(__p)的实现,其完整代码如下

  // Counted ptr with no deleter or allocator support
  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;
    };

_M_ptr为一个指针,其在构造时由__shared_count传入,指向shared_ptr所对应的内存空间,因此我们可以发现,内存管理的任务全部交由_M_pi处理。

      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();
              }
	  }
      }

_M_dispose()执行完之后,会再对&_M_weak_count - 1并判断,如果原始值为0则销毁_M_pi
这里不直接销毁的原因是weak_ptr也会引用这个内存数据,我们必须在weak_ptr都不进行引用时才能对其销毁。

  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
shared_ptr是C++11中引入的一种智能指针,用于管理动态分配的内存。它可以自动释放内存,避免了手动释放内存的繁琐过程,同时也避免了内存泄漏和悬空指针的问题。 shared_ptr源码实现主要包括两个部分:引用计数和内存释放。 引用计数是指在多个shared_ptr对象共享同一个指针时,通过计数器来记录指针被引用的次数。每当一个新的shared_ptr对象指向该指针时,计数器加1;当一个shared_ptr对象被销毁时,计数器减1。当计数器为0时,表示没有任何shared_ptr对象指向该指针,此时可以释放该指针所指向的内存。 内存释放是指在引用计数为0时,释放该指针所指向的内存。shared_ptr使用delete操作符来释放内存,因此需要保证该指针是通过new操作符分配的内存。 下面是shared_ptr的简化版源码实现: ```c++ template<typename T> class shared_ptr { public: shared_ptr(T* ptr = nullptr) : ptr_(ptr), count_(new int(1)) {} ~shared_ptr() { if (--(*count_) == 0) { delete ptr_; delete count_; } } shared_ptr(const shared_ptr& other) : ptr_(other.ptr_), count_(other.count_) { ++(*count_); } shared_ptr& operator=(const shared_ptr& other) { if (this != &other) { if (--(*count_) == 0) { delete ptr_; delete count_; } ptr_ = other.ptr_; count_ = other.count_; ++(*count_); } return *this; } private: T* ptr_; int* count_; }; ``` 在上面的代码中,ptr_表示指向动态分配内存的指针,count_表示引用计数。构造函数中,如果ptr_为空,则count_初始化为1;否则,count_初始化为新分配的int类型指针,其值为1。析构函数中,如果引用计数为0,则释放ptr_和count_所指向的内存。拷贝构造函数赋值运算符重载函数中,需要先将原有的引用计数减1,然后再将新的引用计数加1。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值