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

