服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++11中的智能指针shared_ptr、weak_ptr源码解析

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

2022-01-12 14:10彼 方 C/C++

本文是基于gcc-4.9.0的源代码进行分析,shared_ptr和weak_ptr是C++11才加入标准的,仅对C++智能指针shared_ptr、weak_ptr源码进行解析,需要读者有一定的C++基础并且对智能指针有所了解

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的实现原理。源代码内容并不是很复杂,没有用到什么很高深的语法糖,但是阅读起来非常绕(因为这三个类的关联错综复杂),这就需要我们有耐心地一步一步去深入学习。

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

原文链接:https://blog.csdn.net/weixin_43798887/article/details/116464334

延伸 · 阅读

精彩推荐