C++ 智能指针最佳实践源码分析
作者:lucasfan,騰訊 IEG Global Pub.Tech. 客戶端工程師
智能指針在 C++11 標準中被引入真正標準庫(C++98 中引入的 auto_ptr 存在較多問題),但目前很多 C++開發者仍習慣用原生指針,視智能指針為洪水猛獸。但很多實際場景下,智能指針卻是解決問題的神器,尤其是一些涉及多線程的場景下。本文將介紹智能指針可以解決的問題,用法及最佳實踐。并且根據源碼分析智能指針的實現原理。
一、為什么需要使用智能指針
1.1 內存泄漏
C++在堆上申請內存后,需要手動對內存進行釋放。代碼的初創者可能會注意內存的釋放,但隨著代碼協作者加入,或者隨著代碼日趨復雜,很難保證內存都被正確釋放。
尤其是一些代碼分支在開發中沒有被完全測試覆蓋的時候,就算是內存泄漏檢查工具也不一定能檢查到內存泄漏。
void?test_memory_leak(bool?open) {A?*a?=?new?A();if(open){//?代碼變復雜過程中,很可能漏了?delete(a);return;}delete(a);return; }1.2 多線程下對象析構問題
多線程遇上對象析構,是一個很難的問題,稍有不慎就會導致程序崩潰。因此在對于 C++開發者而言,經常會使用靜態單例來使得對象常駐內存,避免析構帶來的問題。這勢必會造成內存泄露,當單例對象比較大,或者程序對內存非常敏感的時候,就必須面對這個問題了。
先以一個常見的 C++多線程問題為例,介紹多線程下的對象析構問題。
比如我們在開發過程中,經常會在一個 Class 中創建一個線程,這個線程讀取外部對象的成員變量。
//?日志上報Class class?ReportClass { private:ReportClass()?{}ReportClass(const?ReportClass&)?=?delete;ReportClass&?operator=(const?ReportClass&)?=?delete;ReportClass(const?ReportClass&&)?=?delete;ReportClass&?operator=(const?ReportClass&&)?=?delete;private:std::mutex?mutex_;int?count_?=?0;void?addWorkThread();public:void?pushEvent(std::string?event);private:static?void?workThread(ReportClass?*report);private:static?ReportClass*?instance_;static?std::mutex?static_mutex_;public:static?ReportClass*?GetInstance();static?void?ReleaseInstance(); };std::mutex?ReportClass::static_mutex_; ReportClass*?ReportClass::instance_;ReportClass*?ReportClass::GetInstance() {//?單例簡單實現,非本文重點std::lock_guard<std::mutex>?lock(static_mutex_);if?(instance_?==?nullptr)?{instance_?=?new?ReportClass();instance_->addWorkThread();}return?instance_; }void?ReportClass::ReleaseInstance() {std::lock_guard<std::mutex>?lock(static_mutex_);if(instance_?!=?nullptr){delete?instance_;instance_?=?nullptr;} }//?輪詢上報線程 void?ReportClass::workThread(ReportClass?*report) {while(true){//?線程運行過程中,report可能已經被銷毀了std::unique_lock<std::mutex>?lock(report->mutex_);if(report->count_?>?0){report->count_--;}usleep(1000*1000);} }//?創建任務線程 void?ReportClass::addWorkThread() {std::thread?new_thread(workThread,?this);new_thread.detach(); }//?外部調用 void?ReportClass::pushEvent(std::string?event) {std::unique_lock<std::mutex>?lock(mutex_);this->count_++; }使用 ReportClass 的代碼如下:
ReportClass::GetInstance()->pushEvent("test");但當這個外部對象(即ReportClass)析構時,對象創建的線程還在執行。此時線程引用的對象指針為野指針,程序必然會發生異常。
解決這個問題的思路是在對象析構的時候,對線程進行join。
//?日志上報Class class?ReportClass { private://...~ReportClass();private://...bool?stop_?=?false;std::thread?*work_thread_;//... };//?輪詢上報線程 void?ReportClass::workThread(ReportClass?*report) {while(true){std::unique_lock<std::mutex>?lock(report->mutex_);//?如果上報停止,不再輪詢上報if(report->stop_){break;}if(report->count_?>?0){report->count_--;}usleep(1000*1000);} }//?創建任務線程 void?ReportClass::addWorkThread() {//?保存線程指針,不再使用分離線程work_thread_?=?new?std::thread(workThread,?this); }ReportClass::~ReportClass() {//?通過join來停止內部線程stop_?=?true;work_thread_->join();delete?work_thread_;work_thread_?=?nullptr; }這種方式看起來沒問題了,但是由于這個對象一般是被多個線程使用。假如某個線程想要釋放這個對象,但另外一個線程還在使用這個對象,可能會出現野指針問題。就算釋放對象的線程將對象釋放后將指針置為nullptr,但仍然可能在多線程下在指針置空前被另外一個線程取得地址并使用。
| ReportClass::GetInstance()->ReleaseInstance(); | ReportClass *report = ReportClass::GetInstance(); if(report) { // 此時切換到線程 A report->pushEvent("test"); } |
此種場景下,鎖機制已經很難解決這個問題。對于多線程下的對象析構問題,智能指針可謂是神器。接下來我們先對智能指針的基本用法進行說明。
二、智能指針的基本用法
智能指針設計的初衷就是可以幫助我們管理堆上申請的內存,可以理解為開發者只需要申請,而釋放交給智能指針。
目前 C++11 主要支持的智能指針為以下幾種
unique_ptr
shared_ptr
weak_ptr
2.1 unique_ptr
先上代碼
class?A { public:void?do_something()?{} };void?test_unique_ptr(bool?open) {std::unique_ptr<A>?a(new?A());a->do_something();if(open){//?不再需要手動釋放內存return;}//?不再需要手動釋放內存return; }unique_ptr的核心特點就如它的名字一樣,它擁有對持有對象的唯一所有權。即兩個unique_ptr不能同時指向同一個對象。
那具體這個唯一所有權如何體現呢?
1、unique_ptr不能被復制到另外一個unique_ptr
2、unique_ptr所持有的對象只能通過轉移語義將所有權轉移到另外一個unique_ptr
std::unique_ptr<A>?a1(new?A()); std::unique_ptr<A>?a2?=?a1;//編譯報錯,不允許復制 std::unique_ptr<A>?a3?=?std::move(a1);//可以轉移所有權,所有權轉義后a1不再擁有任何指針智能指針有一個通用的規則,就是->表示用于調用指針原有的方法,而.則表示調用智能指針本身的方法。
unique_ptr本身擁有的方法主要包括:
1、get() 獲取其保存的原生指針,盡量不要使用
2、bool() 判斷是否擁有指針
3、release() 釋放所管理指針的所有權,返回原生指針。但并不銷毀原生指針。
4、reset() 釋放并銷毀原生指針。如果參數為一個新指針,將管理這個新指針
std::unique_ptr<A>?a1(new?A()); A?*origin_a?=?a1.get();//盡量不要暴露原生指針 if(a1) {//?a1?擁有指針 }std::unique_ptr<A>?a2(a1.release());//常見用法,轉義擁有權 a2.reset(new?A());//釋放并銷毀原有對象,持有一個新對象 a2.reset();//釋放并銷毀原有對象,等同于下面的寫法 a2?=?nullptr;//釋放并銷毀原有對象2.2 shared_ptr
與unique_ptr的唯一所有權所不同的是,shared_ptr強調的是共享所有權。也就是說多個shared_ptr可以擁有同一個原生指針的所有權。
std::shared_ptr<A>?a1(new?A()); std::shared_ptr<A>?a2?=?a1;//編譯正常,允許所有權的共享shared_ptr 是通過引用計數的方式管理指針,當引用計數為 0 時會銷毀擁有的原生對象。
shared_ptr本身擁有的方法主要包括:
1、get() 獲取其保存的原生指針,盡量不要使用
2、bool() 判斷是否擁有指針
3、reset() 釋放并銷毀原生指針。如果參數為一個新指針,將管理這個新指針
4、unique() 如果引用計數為 1,則返回 true,否則返回 false
5、use_count() 返回引用計數的大小
std::shared_ptr<A>?a1(new?A()); std::shared_ptr<A>?a2?=?a1;//編譯正常,允許所有權的共享A?*origin_a?=?a1.get();//盡量不要暴露原生指針if(a1) {//?a1?擁有指針 }if(a1.unique()) {//?如果返回true,引用計數為1 }long?a1_use_count?=?a1.use_count();//引用計數數量2.3 weak_ptr
weak_ptr 比較特殊,它主要是為了配合shared_ptr而存在的。就像它的名字一樣,它本身是一個弱指針,因為它本身是不能直接調用原生指針的方法的。如果想要使用原生指針的方法,需要將其先轉換為一個shared_ptr。那weak_ptr存在的意義到底是什么呢?
由于shared_ptr是通過引用計數來管理原生指針的,那么最大的問題就是循環引用(比如 a 對象持有 b 對象,b 對象持有 a 對象),這樣必然會導致內存泄露。而weak_ptr不會增加引用計數,因此將循環引用的一方修改為弱引用,可以避免內存泄露。
weak_ptr可以通過一個shared_ptr創建。
std::shared_ptr<A>?a1(new?A()); std::weak_ptr<A>?weak_a1?=?a1;//不增加引用計數weak_ptr本身擁有的方法主要包括:
1、expired() 判斷所指向的原生指針是否被釋放,如果被釋放了返回 true,否則返回 false
2、use_count() 返回原生指針的引用計數
3、lock() 返回 shared_ptr,如果原生指針沒有被釋放,則返回一個非空的 shared_ptr,否則返回一個空的 shared_ptr
4、reset() 將本身置空
std::shared_ptr<A>?a1(new?A()); std::weak_ptr<A>?weak_a1?=?a1;//不增加引用計數if(weak_a1.expired()) {//如果為true,weak_a1對應的原生指針已經被釋放了 }long?a1_use_count?=?weak_a1.use_count();//引用計數數量if(std::shared_ptr<A>?shared_a?=?weak_a1.lock()) {//此時可以通過shared_a進行原生指針的方法調用 }weak_a1.reset();//將weak_a1置空三、智能指針的最佳實踐
以上只是智能指針的基本用法,但是真正上手實踐的時候,卻發現程序在不經意間崩潰了。踩過了幾次坑后,很多同學就罵罵咧咧的放棄了(什么辣雞東西)。因此想要用好智能指針還需要進一步了解智能指針,甚至需要了解智能指針源碼實現。
這一節我們會基于基本用法,進一步說明智能指針的實踐用法,一起馴服智能指針這頭野獸。
3.1 智能指針如何選擇
在介紹指針如何選擇之前,我們先回顧一下這幾個指針的特點
1、unique_ptr獨占對象的所有權,由于沒有引用計數,因此性能較好
2、shared_ptr共享對象的所有權,但性能略差
3、weak_ptr配合shared_ptr,解決循環引用的問題
由于性能問題,那么可以粗暴的理解:優先使用unique_ptr。但由于unique_ptr不能進行復制,因此部分場景下不能使用的。
3.1.1 unique_ptr 的使用場景
unique_ptr一般在不需要多個指向同一個對象的指針時使用。但這個條件本身就很難判斷,在我看來可以簡單的理解:這個對象在對象或方法內部使用時優先使用unique_ptr。
1、對象內部使用
class?TestUnique { private:std::unique_ptr<A>?a_?=?std::unique_ptr<A>(new?A()); public:void?process1(){a_->do_something();}void?process2(){a_->do_something();}~TestUnique(){//此處不再需要手動刪除a_} };2、方法內部使用
void?test_unique_ptr() {std::unique_ptr<A>?a(new?A());a->do_something(); }3.1.2 shared_ptr 的使用場景及最佳實踐
shared_ptr一般在需要多個執行同一個對象的指針使用。在我看來可以簡單的理解:這個對象需要被多個 Class 同時使用的時候。
class?B { private:std::shared_ptr<A>?a_;public:B(std::shared_ptr<A>&?a):?a_(a)?{} };class?C { private:std::shared_ptr<A>?a_;public:C(std::shared_ptr<A>&?a):?a_(a)?{} };std::shared_ptr<B>?b_; std::shared_ptr<C>?c_;void?test_A_B_C() {std::shared_ptr<A>?a?=?std::make_shared<A>();b_?=?std::make_shared<B>(a);c_?=?std::make_shared<C>(a); }在上面的代碼中需要注意,我們使用std::make_shared代替new的方式創建shared_ptr。
因為使用new的方式創建shared_ptr會導致出現兩次內存申請,而std::make_shared在內部實現時只會申請一個內存。因此建議后續均使用std::make_shared。
如果A想要調用B和C的方法怎么辦呢?可否在A中定義B和C的shared_ptr呢?答案是不可以,這樣會產生循環引用,導致內存泄露。
此時就需要weak_ptr出場了。
class?A { private:std::weak_ptr<B>?b_;std::weak_ptr<C>?c_; public:void?do_something()?{}void?set_B_C(const?std::shared_ptr<B>&?b,?const?std::shared_ptr<C>&?c){b_?=?b;c_?=?c;} };a->set_B_C(b_,?c_);如果想要在A內部將當前對象的指針共享給其他對象,需要怎么處理呢?
class?D { private:std::shared_ptr<A>?a_;public:std::shared_ptr<A>&?a):?a_(a)?{} };class?A { //上述代碼省略public:void?new_D(){//錯誤方式,用this指針重新構造shared_ptr,將導致二次釋放當前對象std::shared_ptr<A>?this_shared_ptr1(this);std::unique_ptr<D>?d1(new?D(this_shared_ptr1));} };如果采用this指針重新構造shared_ptr是肯定不行的,因為重新創建的shared_ptr與當前對象的shared_ptr沒有關系,沒有增加當前對象的引用計數。這將導致任何一個shared_ptr計數為 0 時提前釋放了對象,后續操作這個釋放的對象都會導致程序異常。
此時就需要引入shared_from_this。對象繼承了enable_shared_from_this后,可以通過shared_from_this()獲取當前對象的shared_ptr指針。
class?A:?public?std::enable_shared_from_this<A> { //上述代碼省略public:void?new_D(){//錯誤方式,用this指針重新構造shared_ptr,將導致二次釋放當前對象std::shared_ptr<A>?this_shared_ptr1(this);std::unique_ptr<D>?d1(new?D(this_shared_ptr1));//正確方式std::shared_ptr<A>?this_shared_ptr2?=?shared_from_this();std::unique_ptr<D>?d2(new?D(this_shared_ptr2));} };3.2 智能指針的錯誤用法
智能指針的使用時有較多常見的錯誤用法,可能會導致程序異常。下面我會列舉這些錯誤用法,開發時需要避免。
1、使用智能指針托管的對象,盡量不要在再使用原生指針
很多開發同學(包括我在內)在最開始使用智能指針的時候,對同一個對象會混用智能指針和原生指針,導致程序異常。
void?incorrect_smart_pointer1() {A?*a=?new?A();std::unique_ptr<A>?unique_ptr_a(a);//?此處將導致對象的二次釋放delete?a; }2、不要把一個原生指針交給多個智能指針管理
如果將一個原生指針交個多個智能指針,這些智能指針釋放對象時會產生對象的多次銷毀
void?incorrect_smart_pointer2() {A?*a=?new?A();std::unique_ptr<A>?unique_ptr_a1(a);std::unique_ptr<A>?unique_ptr_a2(a);//?此處將導致對象的二次釋放 }3、盡量不要使用 get()獲取原生指針
void?incorrect_smart_pointer3() {std::shared_ptr<A>?shared_ptr_a1?=?std::make_shared<A>();A?*a=?shared_ptr_a1.get();std::shared_ptr<A>?shared_ptr_a2(a);//?此處將導致對象的二次釋放delete?a;//?此處也將導致對象的二次釋放 }4、不要將 this 指針直接托管智能指針
class?E {void?use_this(){//錯誤方式,用this指針重新構造shared_ptr,將導致二次釋放當前對象std::shared_ptr<E>?this_shared_ptr1(this);} };std::shared_ptr<E>?e?=?std::make_shared<E>();5、智能指針只能管理堆對象,不能管理棧上對象
棧上對象本身在出棧時就會被自動銷毀,如果將其指針交給智能指針,會造成對象的二次銷毀
void?incorrect_smart_pointer5() {int?int_num?=?3;std::unique_ptr<int>?int_unique_ptr(&int_num); }3.3 解決多線程下對象析構問題
有了智能指針之后,我們就可以使用智能指針解決多線程下的對象析構問題。
我們使用shared_ptr管理ReportClass。并將 weak_ptr傳給子線程,子線程會判斷外部的ReportClass是否已經被銷毀,如果沒有被銷毀會通過weak_ptr換取shared_ptr,否則線程退出。解決了外部對象銷毀,內部線程使用外部對象的野指針的問題。
//?日志上報Class class?ReportClass:?public?std::enable_shared_from_this<ReportClass> {//...private:static?void?workThread(std::weak_ptr<ReportClass>?weak_report_ptr);private:static?std::shared_ptr<ReportClass>?instance_;static?std::mutex?static_mutex_;public:static?std::shared_ptr<ReportClass>?GetInstance();static?void?ReleaseInstance(); };std::mutex?ReportClass::static_mutex_; std::shared_ptr<ReportClass>?ReportClass::instance_;std::shared_ptr<ReportClass>?ReportClass::GetInstance() {//?單例簡單實現,非本文重點std::lock_guard<std::mutex>?lock(static_mutex_);if?(!instance_)?{instance_?=?std::shared_ptr<ReportClass>(new?ReportClass());instance_->addWorkThread();}return?instance_; }void?ReportClass::ReleaseInstance() {std::lock_guard<std::mutex>?lock(static_mutex_);if(instance_){instance_.reset();} }//?輪詢上報線程 void?ReportClass::workThread(std::weak_ptr<ReportClass>?weak_report_ptr) {while(true){std::shared_ptr<ReportClass>?shared_report_ptr?=?weak_report_ptr.lock();if(!shared_report_ptr){return;}std::unique_lock<std::mutex>(shared_report_ptr->mutex_);if(shared_report_ptr->count_?>?0){shared_report_ptr->count_--;}usleep(1000*1000);} }//?創建任務線程 void?ReportClass::addWorkThread() {std::weak_ptr<ReportClass>?weak_report_ptr?=?shared_from_this();std::thread?work_thread(workThread,?weak_report_ptr);work_thread.detach(); }//?外部調用 void?ReportClass::pushEvent(std::string?event) {std::unique_lock<std::mutex>?lock(mutex_);this->count_++; }并且在多個線程使用的時候,由于采用shared_ptr管理,因此只要有shared_ptr持有對象,就不會銷毀對象,因此不會出現多個線程使用時對象被析構的情況。只有該對象的所有shared_ptr都被銷毀的時候,對象的內存才會被釋放,保證的對象析構的安全。
四、智能指針源碼解析
在介紹智能指針源碼前,需要明確的是,智能指針本身是一個棧上分配的對象。根據棧上分配的特性,在離開作用域后,會自動調用其析構方法。智能指針根據這個特性實現了對象內存的管理和自動釋放。
本文所分析的智能指針源碼基于 Android ndk-16b 中 llvm-libc++的 memory 文件。
4.1 unique_ptr
先看下 unique_ptr的聲明。unique_ptr有兩個模板參數,分別為_Tp和_Dp。
_Tp表示原生指針的類型。
_Dp則表示析構器,開發者可以自定義指針銷毀的代碼。其擁有一個默認值default_delete<_Tp>,其實就是標準的delete函數。
函數聲明中typename __pointer_type<_Tp, deleter_type>::type可以簡單理解為_Tp*,即原生指針類型。
template <class _Tp, class _Dp = default_delete<_Tp> > class _LIBCPP_TEMPLATE_VIS unique_ptr { public:typedef _Tp element_type;typedef _Dp deleter_type;typedef typename __pointer_type<_Tp, deleter_type>::type pointer;//... }unique_ptr中唯一的數據成員就是原生指針和析構器的 pair。
private:__compressed_pair<pointer,?deleter_type>?__ptr_;下面看下unique_ptr的構造函數。
template?<class?_Tp,?class?_Dp?=?default_delete<_Tp>?> class?_LIBCPP_TEMPLATE_VIS?unique_ptr?{public://?默認構造函數,用pointer的默認構造函數初始化__ptr_constexpr?unique_ptr()?noexcept?:?__ptr_(pointer())?{}//?空指針的構造函數,同上constexpr?unique_ptr(nullptr_t)?noexcept?:?__ptr_(pointer())?{}//?原生指針的構造函數,用原生指針初始化__ptr_explicit?unique_ptr(pointer?__p)?noexcept?:?__ptr_(__p)?{}//?原生指針和析構器的構造函數,用這兩個參數初始化__ptr_,當前析構器為左值引用unique_ptr(pointer?__p,?_LValRefType<_Dummy>?__d)?noexcept:?__ptr_(__p,?__d)?{}//?原生指針和析構器的構造函數,析構器使用轉移語義進行轉移unique_ptr(pointer?__p,?_GoodRValRefType<_Dummy>?__d)?noexcept:?__ptr_(__p,?_VSTD::move(__d))?{static_assert(!is_reference<deleter_type>::value,"rvalue?deleter?bound?to?reference");}//?移動構造函數,取出原有unique_ptr的指針和析構器進行構造unique_ptr(unique_ptr&&?__u)?noexcept:?__ptr_(__u.release(),?_VSTD::forward<deleter_type>(__u.get_deleter()))?{}//?移動賦值函數,取出原有unique_ptr的指針和析構器進行構造unique_ptr&?operator=(unique_ptr&&?__u)?_NOEXCEPT?{reset(__u.release());__ptr_.second()?=?_VSTD::forward<deleter_type>(__u.get_deleter());return?*this;}}再看下unique_ptr幾個常用函數的實現。
template?<class?_Tp,?class?_Dp?=?default_delete<_Tp>?> class?_LIBCPP_TEMPLATE_VIS?unique_ptr?{//?返回原生指針 pointer?get()?const?_NOEXCEPT?{return?__ptr_.first(); }//?判斷原生指針是否為空 _LIBCPP_EXPLICIT?operator?bool()?const?_NOEXCEPT?{return?__ptr_.first()?!=?nullptr; }//?將__ptr置空,并返回原有的指針 pointer?release()?_NOEXCEPT?{pointer?__t?=?__ptr_.first();__ptr_.first()?=?pointer();return?__t; }//?重置原有的指針為新的指針,如果原有指針不為空,對原有指針所指對象進行銷毀 void?reset(pointer?__p?=?pointer())?_NOEXCEPT?{pointer?__tmp?=?__ptr_.first();__ptr_.first()?=?__p;if?(__tmp)__ptr_.second()(__tmp); } }再看下unique_ptr指針特性的兩個方法。
//?返回原生指針的引用 typename?add_lvalue_reference<_Tp>::type operator*()?const?{return?*__ptr_.first(); } //?返回原生指針 pointer?operator->()?const?_NOEXCEPT?{return?__ptr_.first(); }最后再看下unique_ptr的析構函數。
//?通過reset()方法進行對象的銷毀 ~unique_ptr()?{?reset();?}4.2 shared_ptr
shared_ptr 與unique_ptr最核心的區別就是比unique_ptr多了一個引用計數,并由于引用計數的加入,可以支持拷貝。
先看下shared_ptr的聲明。shared_ptr主要有兩個成員變量,一個是原生指針,一個是控制塊的指針,用來存儲這個原生指針的shared_ptr和weak_ptr的數量。
template<class?_Tp> class?shared_ptr { public:typedef?_Tp?element_type;private:element_type*??????__ptr_;__shared_weak_count*?__cntrl_;//... }我們重點看下__shared_weak_count的定義。
//?共享計數類 class?__shared_count {__shared_count(const?__shared_count&);__shared_count&?operator=(const?__shared_count&);protected://?共享計數long?__shared_owners_;virtual?~__shared_count(); private://?引用計數變為0的回調,一般是進行內存釋放virtual?void?__on_zero_shared()?_NOEXCEPT?=?0;public://?構造函數,需要注意內部存儲的引用計數是從0開始,外部看到的引用計數其實為1explicit?__shared_count(long?__refs?=?0)?_NOEXCEPT:?__shared_owners_(__refs)?{}//?增加共享計數void?__add_shared()?_NOEXCEPT?{__libcpp_atomic_refcount_increment(__shared_owners_);}//?釋放共享計數,如果共享計數為0(內部為-1),則調用__on_zero_shared進行內存釋放bool?__release_shared()?_NOEXCEPT?{if?(__libcpp_atomic_refcount_decrement(__shared_owners_)?==?-1)?{__on_zero_shared();return?true;}return?false;}//?返回引用計數,需要對內部存儲的引用計數+1處理long?use_count()?const?_NOEXCEPT?{return?__libcpp_relaxed_load(&__shared_owners_)?+?1;} };class?__shared_weak_count:?private?__shared_count {//?weak?ptr計數long?__shared_weak_owners_;public://?內部共享計數和weak計數都為0explicit?__shared_weak_count(long?__refs?=?0)?_NOEXCEPT:?__shared_count(__refs),__shared_weak_owners_(__refs)?{} protected:virtual?~__shared_weak_count();public://?調用通過父類的__add_shared,增加共享引用計數void?__add_shared()?_NOEXCEPT?{__shared_count::__add_shared();}//?增加weak引用計數void?__add_weak()?_NOEXCEPT?{__libcpp_atomic_refcount_increment(__shared_weak_owners_);}//?調用父類的__release_shared,如果釋放了原生指針的內存,還需要調用__release_weak,因為內部weak計數默認為0void?__release_shared()?_NOEXCEPT?{if?(__shared_count::__release_shared())__release_weak();}//?weak引用計數減1void?__release_weak()?_NOEXCEPT;//?獲取共享計數long?use_count()?const?_NOEXCEPT?{return?__shared_count::use_count();}__shared_weak_count*?lock()?_NOEXCEPT;private://?weak計數為0的處理virtual?void?__on_zero_shared_weak()?_NOEXCEPT?=?0; };其實__shared_weak_count也是虛類,具體使用的是__shared_ptr_pointer。__shared_ptr_pointer中有一個成員變量__data_,用于存儲原生指針、析構器、分配器。__shared_ptr_pointer繼承了__shared_weak_count,因此它就主要負責內存的分配、銷毀,引用計數。
class?__shared_ptr_pointer:?public?__shared_weak_count {__compressed_pair<__compressed_pair<_Tp,?_Dp>,?_Alloc>?__data_; public:_LIBCPP_INLINE_VISIBILITY__shared_ptr_pointer(_Tp?__p,?_Dp?__d,?_Alloc?__a):??__data_(__compressed_pair<_Tp,?_Dp>(__p,?_VSTD::move(__d)),?_VSTD::move(__a))?{}#ifndef?_LIBCPP_NO_RTTIvirtual?const?void*?__get_deleter(const?type_info&)?const?_NOEXCEPT; #endifprivate:virtual?void?__on_zero_shared()?_NOEXCEPT;virtual?void?__on_zero_shared_weak()?_NOEXCEPT; };了解了引用計數的基本原理后,再看下shared_ptr的實現。
//?使用原生指針構造shared_ptr時,會構建__shared_ptr_pointer的控制塊 shared_ptr<_Tp>::shared_ptr(_Yp*?__p,typename?enable_if<is_convertible<_Yp*,?element_type*>::value,?__nat>::type):?__ptr_(__p) {unique_ptr<_Yp>?__hold(__p);typedef?typename?__shared_ptr_default_allocator<_Yp>::type?_AllocT;typedef?__shared_ptr_pointer<_Yp*,?default_delete<_Yp>,?_AllocT?>?_CntrlBlk;__cntrl_?=?new?_CntrlBlk(__p,?default_delete<_Yp>(),?_AllocT());__hold.release();__enable_weak_this(__p,?__p); }//?如果進行shared_ptr的拷貝,會增加引用計數 template<class?_Tp> inline shared_ptr<_Tp>::shared_ptr(const?shared_ptr&?__r)?_NOEXCEPT:?__ptr_(__r.__ptr_),__cntrl_(__r.__cntrl_) {if?(__cntrl_)__cntrl_->__add_shared(); }//?銷毀shared_ptr時,會使共享引用計數減1,如果減到0會銷毀內存 template<class?_Tp> shared_ptr<_Tp>::~shared_ptr() {if?(__cntrl_)__cntrl_->__release_shared(); }4.3 weak_ptr
了解完shared_ptr,weak_ptr也就比較簡單了。weak_ptr也包括兩個對象,一個是原生指針,一個是控制塊。雖然weak_ptr內存儲了原生指針,不過由于未實現operator->因此不能直接使用。
class?_LIBCPP_TEMPLATE_VIS?weak_ptr { public:typedef?_Tp?element_type; private:element_type*????????__ptr_;__shared_weak_count*?__cntrl_;}//?通過shared_ptr構造weak_ptr。會將shared_ptr的成員變量地址進行復制。增加weak引用計數 weak_ptr<_Tp>::weak_ptr(shared_ptr<_Yp>?const&?__r,typename?enable_if<is_convertible<_Yp*,?_Tp*>::value,?__nat*>::type)_NOEXCEPT:?__ptr_(__r.__ptr_),__cntrl_(__r.__cntrl_) {if?(__cntrl_)__cntrl_->__add_weak(); }//?weak_ptr析構器 template<class?_Tp> weak_ptr<_Tp>::~weak_ptr() {if?(__cntrl_)__cntrl_->__release_weak(); }最近熱文:
淺談 K8s 網絡模型CNI協議
提速 30%!騰訊TQUIC 網絡傳輸協議
大牛書單 | 消息隊列方向的好書
總結
以上是生活随笔為你收集整理的C++ 智能指针最佳实践源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 iframe 的全新微前端方案
- 下一篇: C++ 学习笔记