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
的實現原理。源代碼內容并不是很復雜,沒有用到什么很高深的語法糖,但是閱讀起來非常繞(因為這三個類的關聯錯綜復雜),這就需要我們有耐心地一步一步去深入學習。
最后,如果大家覺得本文寫得好的話麻煩點贊收藏關注一下謝謝,也可以關注該專欄,以后會有更多優質文章輸出的。
原文鏈接:https://blog.csdn.net/weixin_43798887/article/details/116464334