C++2.0 shared_ptr和weak_ptr深入刨析

在gnu c++中讨论shared_ptr和weak_ptr只需要讨论__shared_count__weak_count,另外就是weak_ptr没有定义operator ->operator *,所以没有像原始指针那样的行为。

首先由简入繁。从简单的weak_ptr开始看起,然后研究shared_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_ptrshared_ptr产生。

1. weak_ptr->weak_ptr

weak_ptrweak_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_ptrweak_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_ptrshared_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_this

template <typename _Tp, _Lock_policy _Lp>
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, _Lp> shared_from_this() {
    return __shared_ptr<_Tp, _Lp>(this->_M_weak_this);
  }

  __shared_ptr<const _Tp, _Lp> shared_from_this() const {
    return __shared_ptr<const _Tp, _Lp>(this->_M_weak_this);
  }

  __weak_ptr<_Tp, _Lp> weak_from_this() noexcept { return this->_M_weak_this; }

  __weak_ptr<const _Tp, _Lp> weak_from_this() const noexcept {
    return this->_M_weak_this;
  }

 private:
  template <typename _Tp1>
  void _M_weak_assign(_Tp1* __p,
                      const __shared_count<_Lp>& __n) const noexcept {
    _M_weak_this._M_assign(__p, __n);
  }

  friend const __enable_shared_from_this* __enable_shared_from_this_base(
      const __shared_count<_Lp>&, const __enable_shared_from_this* __p) {
    return __p;
  }

  template <typename, _Lock_policy>
  friend class __shared_ptr;

  mutable __weak_ptr<_Tp, _Lp> _M_weak_this;
};

可以看到这个类,含有一个数据成员:_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 <typename _Tp1>
  void _M_weak_assign(_Tp1* __p,
                      const __shared_count<_Lp>& __n) const noexcept {
    _M_weak_this._M_assign(__p, __n);
  }

再看一下构造shared_ptr时,入参为enable_shared_frome_this的构造函数:

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

template <typename _Yp, typename _Yp2 = typename remove_cv<_Yp>::type>
typename enable_if<__has_esft_base<_Yp2>::value>::type
_M_enable_shared_from_this_with(_Yp* __p) noexcept {
  if (auto __base = __enable_shared_from_this_base(_M_refcount, __p))
    __base->_M_weak_assign(const_cast<_Yp2*>(__p), _M_refcount);
}

friend const __enable_shared_from_this* __enable_shared_from_this_base(
    const __shared_count<_Lp>&, const __enable_shared_from_this* __p) {
  return __p;
}

在初次使用继承了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<void()> callback) {
    std::async([=] {
      sleep(100);
      callback();
    });    
  }
};

struct A : std::enable_shared_from_this<A> {
  B b_;
  auto callBack() {}
  auto func() { b_.asyncFunc(std::bind(&A::callBack, shared_frome_this())); }
};

{
  auto a = make_shared<A>();
  a->func();
}

总结weak_ptr的作用

  1. 解决循环引用
  2. 实现弱回调,不延长对象生命周期,只作为对shared_ptr的观测。回调前判断提升结果,提升成功才做回调。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歪锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值