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