仿現(xiàn)代C++智能指針實現(xiàn)引用計數(shù)
首先,提個問題,何為引用計數(shù),為什么要去實現(xiàn)引用計數(shù)?
智能指針最大的作用就是協(xié)助開發(fā)者管理內(nèi)存,防止開發(fā)者遺漏釋放或者錯誤釋放已申請的內(nèi)存空間,最大限度保證內(nèi)存安全。使用智能指針可免除非常多的經(jīng)典 C++ 版本帶來的煩惱,比如招人恨的 double free or corruption。
據(jù)微軟內(nèi)部統(tǒng)計,C++ 應(yīng)用的絕大部分問題屬于內(nèi)存安全問題,所以保障內(nèi)存安全的措施真的非常重要。
那么,智能指針是如何保證內(nèi)存安全的呢?
智能指針在初始化時會托管傳入的內(nèi)存空間,在后續(xù)的流程中,一旦識別到被托管的內(nèi)存不再被需要的時候,自動釋放這塊內(nèi)存。
如何識別被托管的內(nèi)存是否還被需要?
通過引用計數(shù),引用計數(shù)就是計數(shù)器,一般用整形變量表示,代表有多少個智能指針已托管該內(nèi)存空間。
已托管的內(nèi)存每增加綁定一個智能指針,該計數(shù)器自動加 1;反之,已托管的內(nèi)存每解綁一個智能指針(比如智能指針超出生命周期,或者主動解除綁定),該計數(shù)器自動減 1。直到計數(shù)器歸 0,這時被托管的內(nèi)存即被判斷為不再需要保留,最后解綁的智能指針負責(zé)釋放該內(nèi)存空間。
其中,可見該計數(shù)器被所有綁定的智能指針共享訪問。
雖然標準庫已經(jīng)提供非常多現(xiàn)成的智能指針可供調(diào)用,但是其中的奧妙還是非常值得我們細細揣摩,下面就開始跟隨筆者一起實現(xiàn)自己的引用計數(shù)幫助類,權(quán)當(dāng)作略微簡陋的自定義智能指針。
先定義兩個類,一個是代表占用內(nèi)存資源的類 Demo,另一個是就是我們自定義的智能指針類 SmartPtr
class Demo {}; class SmartPtr {};
資源類附加屬性
由于綁定同一個內(nèi)存資源的所有智能指針是共享訪問該內(nèi)存的,所以可以在類 Demo 中附加定義計數(shù)器 count_,計數(shù)器作為專用屬性應(yīng)該帶有隱私屬性也就是不允許隨意被類外訪問,所以同時應(yīng)該被修飾為 private。
在類 Demo 實例化時,計數(shù)器 count_ 應(yīng)該被清零,然后才能被 SmartPtr 托管。所以要求類 Demo 的所有構(gòu)造函數(shù)都初始化 count_ 為 0。
class Demo { public: Demo() : count_(0) {}; private: unsigned int count_; };
作為托管方的類 SmartPtr 需要有權(quán)限可以直接讀寫類 Demo 的私有成員 count_,所以在類 Demo 中還要聲明類 SmartPtr 為友元類
class Demo { // ... private: // ... friend class SmartPtr; };
關(guān)于代表資源的類的簡單改造就介紹完了。
自定義智能指針類
智能指針類 SmartPtr 作為托管方,需要實現(xiàn)基本的功能接口,包括托管、解綁、訪問資源成員、獲取資源引用等。
托管
智能指針需要托管某個內(nèi)存資源,那么就需要在內(nèi)部有對應(yīng)的指針指向資源。同樣為了不必要的暴露,這個指針需要用 private 修飾。
class SmartPtr { private: Demo *p_; };
在托管資源時,資源對應(yīng)的引用計數(shù)器應(yīng)該自增 1,由于我們定義的智能指針在資源類的內(nèi)部被聲明為友元類,所以智能指針綁定資源時可以直接讀寫資源的計數(shù)器。
智能指針如何綁定資源,或者說如何接收將要被托管的資源呢?
資源未被托管
如果資源當(dāng)前沒有被任何智能指針托管,那么,可以在實例化智能指針對象時,利用智能指針的構(gòu)造函數(shù)接收資源指針,并保存到內(nèi)部指針變量,按照上面的代碼就是保存到變量 p_。
class SmartPtr { // ... public: SmartPtr(Demo *p) : p_(p) { ++ p_->count_; } };
分享托管
如果資源當(dāng)前正在被其它智能指針托管,那么,可以從其它的智能指針分享過來。
有兩種分享的方法,一是創(chuàng)建一個新的智能指針來接收其它智能指針的分享,需要用到拷貝構(gòu)造函數(shù),并傳入其它的智能指針的引用實例。
class SmartPtr { // ... public: SmartPtr(const SmartPtr &obj) : p_(obj.p_) { ++ p_->count_; } };
二是,如果有個智能指針實例當(dāng)前已經(jīng)托管某個資源,但是需要重新綁定其它資源,并且當(dāng)前托管的資源需要解綁,可以利用拷貝賦值運算符,右側(cè)操作數(shù)就是其它分享資源的智能指針,左側(cè)操作數(shù)是當(dāng)前的智能指針。
解綁當(dāng)前托管的資源,除了要清除原有的資源指針備份,還需要對資源的計數(shù)器減 1,并且判斷計數(shù)器減 1 后是否歸零,如果歸零就需要釋放該資源占用的內(nèi)存
class SmartPtr { // ... public: SmartPtr& operator=(const SmartPtr& sp) { Demo *p = p_; p_ = sp.p_; ++ p_->count_; if (0 == (-- p->count_)) { delete p; } return *this; } };
生命周期的結(jié)束
上面提到更換托管資源時,還需要解綁自身原有托管的資源。其實當(dāng)智能指針對象自身的生命周期結(jié)束之時,也就是調(diào)用釋構(gòu)函數(shù)時,也需要解綁自身原有托管的資源,這時的解綁主要做的就是對資源的計數(shù)器減 1,并且判斷計數(shù)器減 1 后是否歸零,如果歸零就需要釋放該資源占用的內(nèi)存
class SmartPtr { // ... public: ~SmartPtr() { if (0 == (-- p_->count_)) { delete p_; } } };
成員訪問
智能指針在托管資源后,資源內(nèi)部的成員應(yīng)該能通過智能指針來訪問,正如前面介紹資源類時,在資源類內(nèi)部聲明智能指針為友元類。通過智能指針來訪問資源類內(nèi)部成員的形式,應(yīng)該類似對象的指針訪問對象成員,需要通過重寫智能指針的成員訪問運算符來實現(xiàn)
class SmartPtr { // ... public: Demo* operator->() { return p_; } };
operator-> ()
是一元右綴操作符,重寫了類成員訪問運算符,返回被托管的資源指針。
當(dāng)智能指針對象調(diào)用該操作符時,比如 p 被聲明為智能指針實例對象,被托管的資源所屬類包含成員 m,那么 p->m
會被編譯器解析成 ((p.operator->)->m
。
獲取資源引用
一般通過對象指針獲取對象引用時,是通過 *
運算符。類似地,需要獲取被托管資源對象的引用,可以重寫 *
運算符實現(xiàn)
class SmartPtr { // ... public: Demo& operator*() { return *p_; } };
避免暴露裸指針
一般不推薦直接調(diào)用資源類的裸指針,盡量避免重寫智能指針的操作符以返回被托管資源的指針,因為暴露的裸指針會被意外地使用而破壞引用計數(shù)的機制,最終破壞實現(xiàn)內(nèi)存安全的努力。
而上面在設(shè)計資源類時,仍然依賴使用 new 操作符創(chuàng)建資源實例并返回裸指針,這無疑是一顆定時炸彈。為了隱藏好裸指針,可以把構(gòu)造函數(shù)聲明為 private,并且添加靜態(tài)成員接口 create() 返回智能指針對象
// .h class SmartPtr; class Demo { public: static SmartPtr create(); private: Demo() : count_(0) {}; unsigned int count_; friend class SmartPtr; }; // ... // .cpp SmartPtr Demo::create() { return new Demo(); }
為什么接口 Demo::create()
內(nèi)部直接 return 類 Demo 對象指針而不是 SmartPtr 對象?
因為前面實現(xiàn)類 SmartPtr 時,它的構(gòu)造函數(shù)就有輸入 Demo 指針的重載形式。所以接口 Demo::create()
聲明返回類型為 SmartPtr 對象時,如果直接返回類 Demo 對象指針,就會隱式調(diào)用類 SmartPtr 的對應(yīng)構(gòu)造函數(shù)創(chuàng)建實例對象并返回。
基于上面的設(shè)計結(jié)果,當(dāng)需要在堆里創(chuàng)建 Demo 實例時,內(nèi)存安全的使用方式就可以是這樣子
SmartPtr ptr(Demo::create());
簡簡單單的思路分析如上,其中實現(xiàn)方法利用的是指針語義。
以上就是仿現(xiàn)代C++智能指針實現(xiàn)引用計數(shù)的詳細內(nèi)容,更多關(guān)于C++引用計數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++的靜態(tài)聯(lián)編和動態(tài)聯(lián)編
本文闡述了靜態(tài)聯(lián)編和動態(tài)聯(lián)編的概念和區(qū)別,通過具體實例分析了實現(xiàn)動態(tài)聯(lián)編的條件,指出了虛函數(shù)是實現(xiàn)動態(tài)聯(lián)編的基礎(chǔ)。2016-03-03解析C++中的for循環(huán)以及基于范圍的for語句使用
這篇文章主要介紹了解析C++中的for循環(huán)以及基于范圍的for語句使用,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2016-01-01C語言數(shù)據(jù)結(jié)構(gòu)之二叉樹的非遞歸后序遍歷算法
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)之二叉樹的非遞歸后序遍歷算法的相關(guān)資料,希望通過本文能幫助到大家,讓大家實現(xiàn)這樣的功能,需要的朋友可以參考下2017-10-10詳解C++中構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)和賦值函數(shù)的區(qū)別和實現(xiàn)
這篇文章主要介紹了C++中構(gòu)造函數(shù),拷貝構(gòu)造函數(shù)和賦值函數(shù)的區(qū)別和實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03關(guān)于C++中構(gòu)造函數(shù)初始化成員列表的總結(jié)
下面小編就為大家?guī)硪黄P(guān)于C++中構(gòu)造函數(shù)初始化成員列表的總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12C++?超詳細分析數(shù)據(jù)結(jié)構(gòu)中的時間復(fù)雜度
時間復(fù)雜度一般指時間復(fù)雜性。?在計算機科學(xué)中,時間復(fù)雜性,又稱時間復(fù)雜度,算法的時間復(fù)雜度是一個函數(shù),它定性描述該算法的運行時間2022-03-03Cocos2d-x UI開發(fā)之CCControlSwitch控件類使用實例
這篇文章主要介紹了Cocos2d-x UI開發(fā)之CCControlSwitch控件類使用實例,本文代碼中含大量注釋講解了CCControlSwitch控件類的使用,需要的朋友可以參考下2014-09-09