C++11中的智能指针shared_ptr、weak_ptr源码解析
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) { } ... }; |
由于源代码过长,这里就只贴出其中一部分进行分析:
- 该类没有类成员
- 该类继承于
__shared_ptr
,构造函数也只是调用了__shared_ptr
的构造函数而已,将接管的普通指针传递给__shared_ptr
- 该类没有重载
*
和->
运算符,从这点看shared_ptr
似乎无法实现普通指针的功能,推测这两个运算符的重载是在父类__shared_ptr
实现的 - 该类没有析构函数,从智能指针最终会自动释放内存的特性来看,释放工作肯定不是在该类进行了,接下来分析父类
__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
)
- 从构造函数看,
_M_ptr
获得了接管的普通指针的值,而_M_refcount
的构造也同样需要这个值 - 重载了
*
和->
运算符,由shared_ptr
继承使用,使得智能指针最终能拥有和普通指针一样行为,尽管智能指针本质上是一个对象 - 从析构函数来看,里面啥也没做,说明接管的普通指针也不是在这里释放的,所以有可能是由
_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
)
- 只有构造函数为
_M_pi
分配了内存,并且该类并没有直接持有从前面一直传递过来的那个普通指针,而是继续将其传递给_M_pi
,所以内存的释放也不是直接在该类进行的。 - 拷贝构造函数没有分配内容,而是把拷贝对象的
_M_pi
直接拿过来了,有点类似于浅拷贝的意思,然后调用了_M_pi
的_M_add_ref_copy
方法(后面会讲),增加了一次引用计数。赋值函数也是同样的道理,但是由于赋值函数的特殊性(当赋值对象原先就存在时调用赋值函数,否则调用拷贝构造函数),要先调用_M_pi
的_M_release
方法(后面会讲)将自己持有的内存释放掉,其余操作和拷贝构造函数是一样的 - 从析构函数中可以看到,里面并没有直接释放掉为
_M_pi
分配的内存,而是调用了_M_pi
的_M_release
方法,可以大概猜测是通过_M_release
方法释放了_M_pi
的内存(delete this指针,后面会讲) - 由于
__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) }; |
从源代码可以获得以下几点信息:
- 有两个类成员:
_M_use_count
(引用计数)、_M_weak_count
(弱引用计数),对这两个数的操作需要具有原子性 _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指针,这点和前面的猜测一致- 从
_M_release
可以看出,智能指针所接管的指针的释放内存工作只和_M_use_count
有关,当_M_use_count
减完时就会将其释放了,而_M_weak_count
也是有作用的,他负责释放_Sp_counted_base
本身,这也就是为什么weak_ptr
可以保证智能指针这个对象有效,但不保证智能指针所引用的指针有效的原因了(这点和shared_ptr、weak_ptr的定义是完全一致的) - 其他的方法就很简单了,比如
_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; }; |
- 从源代码中可以看到
_Sp_counted_ptr
是_Sp_counted_base
的派生类,并且__shared_count
在初始化_M_pi
时用的也是_Sp_counted_ptr
。 - 接着看
_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
),这样整个流程也就解释通了:
__shared_count
的成员_M_pi
只会初始化一次(构造函数中分配内存初始化的)- 后面调用拷贝构造时(这个行为由
__shared_ptr
触发,__shared_ptr
的拷贝构造函数和赋值函数都会调用__shared_count
的拷贝构造函数),__shared_count
只是简单复制了_M_pi
而已,并没有重新分配内存,然后再调用_M_add_ref_copy
增加一次引用计数,这样就实现了shared_ptr
每多一份拷贝就增加一次引用计数的特性了 - 每一个
__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 ); } } |
从源代码中可以看出以下几点:
- 该类没有类成员
- 从构造函数的参数来看(无参构造函数除外),只能使用
shared_ptr
或weak_ptr
来构造一个weak_ptr
对象,包括赋值函数也是这样的,这就和shared_ptr
有很大区别了,从4.1.1小节
可以看到shared_ptr
是可以使用普通指针来构造的 - 可以调用
lock
方法来获得一个shared_ptr
,lock
方法的实现后面再讲
该类没有重载*
和->
运算符,接下来分析其父类__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. } |
从源代码中可以看出以下几点信息:
- 有两个类成员:
_M_ptr
(由智能指针接管的普通指针)、_M_refcount
(弱引用计数器,类型为__weak_count) - 从构造函数看,
_M_ptr
获得了接管的普通指针的值,而_M_refcount
的构造并不需要这个值了(这点和__shared_ptr
不一样了),_M_refcount
只能借助其他__shared_ptr
的_M_refcount
或者__weak_ptr
的_M_refcount
来进行构造(注意这两个的_M_refcount
类型不同,说明__weak_count
支持多种类型进行构造) - 拷贝构造函数和赋值函数的实现同上
- 该类依然没有重载
*
和->
运算符,由于接下去已无继承关系,所以weak_ptr
不具备普通指针的特性,无法直接使用资源,这点符合weak_ptr
的定义 - 既然
weak_ptr
无法直接使用资源,那他设计_M_ptr
这个成员的意图在哪里呢?答案就是lock
方法将weak_ptr
转换为shared_ptr
时是需要将这个指针传递过去的,不然连接管的指针都没了转换的意义也就没了 - 析构函数啥也没做,因为
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; } |
从源代码可以获得以下几点信息:
- 有一个类成员:
_M_pi
(计数器,类型为_Sp_counted_base) - 仔细一看
__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
方法增加一次引用计数
- 构造函数、拷贝构造函数、赋值函数均不为
_M_pi
分配了内存,这点也可以看出weak_ptr
确实是shared_ptr
的附属品而已,自己不持有资源不控制资源 - 析构函数中调用了
_M_pi
的_M_weak_release
方法,释放了_M_pi
的内存(条件满足的情况下才会释放) - 接下来的内容和
3.1.4小节
还有3.1.5小节
的内容是一样的,这里就不赘述
4.2.4、回过头看weak_ptr中lock方法的实现
weak_ptr
的lock
方法调用了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_ptr
和weak_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_ptr
的lock
方法的实现原理就全部讲解完毕。
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; }; |
从源代码可以获得以下几点信息:
- 有一个类成员:
_M_weak_this
- 该类需要被继承,被需要用智能指针管理的对象继承
- 我们平时就是使用该类的
shared_from_this
方法的,可以看到其实现就是利用_M_weak_this
构造一个shared_ptr
对象而已 - 该类并没有直接初始化
_M_weak_this
,而是提供了_M_weak_assign
方法来构造_M_weak_this
,其实现比较简单,就是调用了weak_ptr
的_M_assign
方法 - 那么问题来了,
_M_weak_assign
方法由谁调用呢?从后面我们可以知道是由一个全局函数__enable_shared_from_this_helper
调用的,该函数有一种重载形式是enable_shared_from_this
的友元函数,从上面的代码中就可以看到了,那唯一一个友元函数就是__enable_shared_from_this_helper
,里面调用了enable_shared_from_this
的_M_weak_assign
方法。 - 而
__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_ptr
、weak_ptr
以及enable_shared_from_this
的实现原理。源代码内容并不是很复杂,没有用到什么很高深的语法糖,但是阅读起来非常绕(因为这三个类的关联错综复杂),这就需要我们有耐心地一步一步去深入学习。
最后,如果大家觉得本文写得好的话麻烦点赞收藏关注一下谢谢,也可以关注该专栏,以后会有更多优质文章输出的。