在进入正文前,你需要知道:

  • 本文不会介绍 shared_ptr 以及 weak_ptr 的使用
  • 本文中的代码和实际有出入,进行了一点简化

shared_ptr

1
2
3
4
5
6
template<class _Tp>
class shared_ptr {
    element_type*      __ptr_;
    __shared_weak_count* __cntrl_;
    // ...
};

__ptr_ 是一个指向了我们在堆上创建的对象的指针,而 __cntrl_ 则为控制块,记录了一系列信息,下面会详细分析。

下面是 __shared_weak_count 的主要代码,可以看到它继承于 __shared_count,拥有一个类型为 long 的成员 __shared_weak_owners_,想必这是用来记录弱引用计数的。 值得注意的是,这个类竟然还有一个 pure virtual 的函数,所以可以断定上面 shared_ptr__shared_weak_count* 只是一个基类指针,实际上肯定还有其他的具体实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class __shared_weak_count
    : private __shared_count
{
    long __shared_weak_owners_;
public:
    void __add_weak();
    void __release_weak();
protected:
    ~__shared_weak_count() override;
private:
    virtual void __on_zero_shared_weak() = 0;
};

我们跟到基类 __shared_count。可以看到它类似的有一个类型为 long 的成员 __shared_owners,由此我们可以知道 shared_ptr 的强引用计数由它控制。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class __shared_count
{
protected:
    long __shared_owners_;
    virtual ~__shared_count();
    // ...
public:
    void __add_shared();
    bool __release_shared();
private:
    virtual void __on_zero_shared() = 0;
};

先抛开上面这些代码,我们来看一个经典的例子,研究下 shared_ptr 会做些什么。

1
std::shared_ptr<int> sp(new int(42));

我们找到对应的构造函数代码:

1
2
3
4
5
6
7
8
explicit shared_ptr(_Yp* __p) : __ptr_(__p) {
    unique_ptr<_Yp> __hold(__p);
    typedef typename __shared_ptr_default_allocator<_Yp>::type _AllocT;
    typedef __shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT> _CntrlBlk;
    __cntrl_ = new _CntrlBlk(__p, __shared_ptr_default_delete<_Tp, _Yp>(), _AllocT());
    __hold.release();
    __enable_weak_this(__p, __p);
}

这下验证了我们上面的猜测,__cntrl_ 只是一个基类指针,实际上我们用 _CntrlBlk (__shared_ptr_pointer<_Yp*, __shared_ptr_default_delete<_Tp, _Yp>, _AllocT>) 初始化了它。

老规矩,跟过去看看它是啥:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
template <class _Tp, class _Dp, class _Alloc>
class __shared_ptr_pointer
    : public __shared_weak_count
{
    __compressed_pair<__compressed_pair<_Tp, _Dp>, _Alloc> __data_;
public:
    __shared_ptr_pointer(_Tp __p, _Dp __d, _Alloc __a)
        :  __data_(__compressed_pair<_Tp, _Dp>(__p, _VSTD::move(__d)), _VSTD::move(__a)) {}
private:
    void __on_zero_shared() override;
    void __on_zero_shared_weak() override;
};

和上面类实例化的代码我们可以分析出,shared_ptr 的控制块除了两个引用计数,还至少储存了以下内容:

  • 所拥有资源的指针,__p
  • 用于析构所持有资源的删除器,默认为 __shared_ptr_default_delete<_Tp, _Yp>()
  • 用于分配内存的 allocator, 默认为 _AllocT()

还实现了基类的两个接口 __on_zero_shared__on_zero_shared_weak。至于他们两个有啥用,下面会详细介绍

下面描述了默认情况下 shared_ptr 的控制块布局(注意下图不准确,不包含虚表,也不考虑 __compressed_pair 对内存布局的影响):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
+----------------------------------+
|    __shared_ptr_pointer          |
|  +----------------------------+  |
|  |  __shared_weak_count       |  |
|  | +--------------------+     |  |
|  | | __shared_count     |     |  |
|  | | * __shared_owners_ |     |  |
|  | +--------------------+     |  |
|  |  * __shared_weak_owners_   |  |
|  +----------------------------+  |
|  * element_ptr                   |
|  * deleter                       |
|  * allocator                     |
+----------------------------------+

我们再来看一下 shared_ptr 的析构函数的代码,处理资源释放一般离不开它:

1
2
3
4
5
~shared_ptr()
{
    if (__cntrl_)
        __cntrl_->__release_shared();
}

这与我们所期待的实际上有些出入。为什么它只“递减”引用计数?如果引用计数为零了怎么办?到底是谁负责释放对象的内存?让我们再回到之前的代码:

1
2
3
4
5
6
7
bool __release_shared() {
  if (__libcpp_atomic_refcount_decrement(__shared_owners_) == -1) {
    __on_zero_shared();
    return true;
  }
  return false;
}

注意这个 __on_zero_shared() 调用,由名字我们可以知道强引用计数为零时它会被调用。而这就是我们真正析构 shared_ptr 控制的对象的地方。而它又是一个纯虚函数,由 __shared_ptr_pointer 实现:

1
2
3
4
5
6
7
template <class _Tp, class _Dp, class _Alloc>
void
__shared_ptr_pointer<_Tp, _Dp, _Alloc>::__on_zero_shared() 
{
    __data_.first().second()(__data_.first().first());
    __data_.first().second().~_Dp();
}

对照上文我们可以知道:

  • __data_.first().second() 就是我们之前提到的析构器,或者说 deleter
  • __data_.first().first() 就是我们所持有资源的指针

故在强引用计数为零时我们做了两件事:

  • 析构我们所持有资源
  • 析构这个析构器自身

看到这里你可能会疑问,不是还有个弱引用计数吗?__shared_ptr_pointer 不是还实现了 __on_zero_shared_weak?它什么时候被调用?要回答这些问题,我们就必须要来看下 weak_ptr 的具体实现了。

weak_ptr

下面是 weak_ptr 实现中我认为比较关键的代码:

1
2
3
4
5
6
7
template<class _Tp>
class weak_ptr
{
private:
    element_type*        __ptr_;
    __shared_weak_count* __cntrl_;
}

一般来说我们会用一个 shared_ptr 来初始化 weak_ptr,像下面这样:

1
2
std::shared_ptr<int> sp(new int(42));
std::weak_tr<int> wp = sp;

下面则是会被调用的相关代码:

1
2
3
4
5
6
7
8
9
template<class _Tp>
inline
weak_ptr<_Tp>::weak_ptr(weak_ptr const& __r)
    : __ptr_(__r.__ptr_),
      __cntrl_(__r.__cntrl_)
{
    if (__cntrl_)
        __cntrl_->__add_weak();
}

可以看到 weak_ptr 本身不分配任何资源,只持有两个指向 shared_ptr 相关成员的指针:

  • __ptr_ 指向所控制的资源对象
  • __cntrl_ 指向之前所所分配的资源控制块

shared_ptr,我们来看下它的析构函数:

1
2
3
4
5
6
template<class _Tp>
weak_ptr<_Tp>::~weak_ptr()
{
    if (__cntrl_)
        __cntrl_->__release_weak();
}

shared_ptr 很像,只不过由 __relase_shared 变成了 __release_weak

这里发生了件很有意思的事情,当我尝试去找 __release_weak 的实现的时候,发现他它竟然不在当前头文件。这让我有点吃惊,因为我想当然地认为它是 header only 的。在使用 GDB 动态跟进后,终于在 libcxx/src/memory.cpp 中找到了它。

 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
void
__shared_weak_count::__release_weak() noexcept
{
    // NOTE: The acquire load here is an optimization of the very
    // common case where a shared pointer is being destructed while
    // having no other contended references.
    //
    // BENEFIT: We avoid expensive atomic stores like XADD and STREX
    // in a common case.  Those instructions are slow and do nasty
    // things to caches.
    //
    // IS THIS SAFE?  Yes.  During weak destruction, if we see that we
    // are the last reference, we know that no-one else is accessing
    // us. If someone were accessing us, then they would be doing so
    // while the last shared / weak_ptr was being destructed, and
    // that's undefined anyway.
    //
    // If we see anything other than a 0, then we have possible
    // contention, and need to use an atomicrmw primitive.
    // The same arguments don't apply for increment, where it is legal
    // (though inadvisable) to share shared_ptr references between
    // threads, and have them all get copied at once.  The argument
    // also doesn't apply for __release_shared, because an outstanding
    // weak_ptr::lock() could read / modify the shared count.
    if (__libcpp_atomic_load(&__shared_weak_owners_, _AO_Acquire) == 0)
    {
        // no need to do this store, because we are about
        // to destroy everything.
        //__libcpp_atomic_store(&__shared_weak_owners_, -1, _AO_Release);
        __on_zero_shared_weak();
    }
    else if (__libcpp_atomic_refcount_decrement(__shared_weak_owners_) == -1)
        __on_zero_shared_weak();
}

最后我们看到 __on_zero_shared_weak,当然默认情况下还是由 __shared_ptr_pointer 实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
template <class _Tp, class _Dp, class _Alloc>
void
__shared_ptr_pointer<_Tp, _Dp, _Alloc>::__on_zero_shared_weak() _NOEXCEPT
{
    typedef typename __allocator_traits_rebind<_Alloc, __shared_ptr_pointer>::type _Al;
    typedef allocator_traits<_Al> _ATraits;
    typedef pointer_traits<typename _ATraits::pointer> _PTraits;

    _Al __a(__data_.second());
    __data_.second().~_Alloc();
    __a.deallocate(_PTraits::pointer_to(*this), 1);
}

我们析构了之前的内存分配器,并释放了在本文最开头时分配的控制块。而这其实又引出了一个新的问题,如果控制块是在 weak_ptr 中的析构函数中释放的,那如果我们没有使用 weak_ptr 只使用了 shared_ptr 呢?

事实上,我们再看一眼之前的 __shared_weak_count::__release_shared()

1
2
3
4
void __release_shared() {
  if (__shared_count::__release_shared())
    __release_weak();
}

原来,当我们的强引用计数为零时,所持有的对象被释放,__shared_count::__release_shared() 返回真,我们接着做一次 __release_weak()。等等等一下!!我们哪来的弱引用计数?刚开始若引用计数不应该是零?怎么会存在释放操作呢?

其实在故事的一开始我们就给出了答案。如果你足够细心的话可以注意到 shared_ptr 的构造函数中有这样一条调用:

1
__enable_weak_this(__p, __p);

我们跟过去:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <class _Yp, class _OrigPtr>
void __enable_weak_this(const enable_shared_from_this<_Yp>* __e, _OrigPtr* __ptr) 
{
    typedef __remove_cv_t<_Yp> _RawYp;
    if (__e && __e->__weak_this_.expired())
    {
        __e->__weak_this_ = shared_ptr<_RawYp>(*this,
            const_cast<_RawYp*>(static_cast<const _Yp*>(__ptr)));
    }
}

enable_shared_from_this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template<class _Tp>
class enable_shared_from_this
{
    mutable weak_ptr<_Tp> __weak_this_;
public:
    shared_ptr<_Tp> shared_from_this()
        {return shared_ptr<_Tp>(__weak_this_);}

    weak_ptr<_Tp> weak_from_this() 
       { return __weak_this_; }
};

// TODO:

  • How does enable_shared_from_this works?
  • Why we release_weak when the strong ref count is 0?
  • enable_weak_from_this in the ctor of shared_ptr
1
2
3
void __add_shared() {
  __libcpp_atomic_refcount_increment(__shared_owners_);
}

从名字里我们可以猜到它是用来递增引用计数的,而这有一个问题,为什么他们用的是原子的操作? // TODO