什么叫RAII(Resource Acquisition Is Initialization)?

創(chuàng)新互聯(lián)公司是一家集網(wǎng)站建設(shè),雁峰企業(yè)網(wǎng)站建設(shè),雁峰品牌網(wǎng)站建設(shè),網(wǎng)站定制,雁峰網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,雁峰網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力。可充分滿足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
RAII指的就是資源分配即初始化,它的作用就是定義一個類來封裝資源的分配和釋放,在構(gòu)造函數(shù)完成資源的分配和初始化,在析構(gòu)函數(shù)完成資源的清理,可以保證資源的正確初始化和釋放。它是一種技術(shù)。
為什么要使用RAII?
在堆上分配空間時,我們必須很仔細(xì)的申請并給出相應(yīng)的釋放語句,但是隨著程序的復(fù)雜度增大,判斷、循環(huán)、遞歸這樣的語句會讓程序走向不確定,很有可能出現(xiàn)申請了沒釋放,申請了多次釋放。所以我們定義了一個類來封裝資源的分配和釋放。
那么什么叫智能指針呢?
智能指針是利用了RAII(資源獲取即初始化)的技術(shù)對普通的指針進(jìn)行封裝,這使得智能指針實質(zhì)是一個對象,行為表現(xiàn)的卻像一個指針。
智能指針的分類:
1、AutoPtr (在函數(shù)庫中都是小寫加下劃線,比如AutoPtr 函數(shù)庫中為auto_ptr)
template <typename T>
class AutoPtr
{
public:
AutoPtr(T* ptr=NULL):_ptr(ptr){}
AutoPtr(AutoPtr<T>& t)
{
_ptr=t._ptr;
t._ptr=NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& t)
{
if(_ptr!=t._ptr)
{
if(_ptr){ delete _ptr; }
_ptr=t._ptr;
t._ptr=NULL;
}
return *this;
}
T& operator*(){ return *_ptr;}
T* operator->(){return _ptr;}
~AutoPtr(){ if(_ptr) { delete _ptr; } }
private:
T* _ptr;
};AutoPtr可以new出空間后,不必delete,出了作用域后會自動釋放。
表面上這看似完美,可現(xiàn)實并不是這樣。它無法像指針那樣同一塊空間被多個指針指向,它只能有一個指針指向一塊空間,當(dāng)發(fā)生拷貝構(gòu)造或者賦值運算符重載時,它會釋放原先的指針。
2、ScopedPtr
ScopedPtr實際上就是把AutoPtr的拷貝構(gòu)造和賦值運算符的重載寫成私有的,不讓用戶訪問,這樣就不會出現(xiàn)同一塊空間被多個指針指向,但是這畢竟是治標(biāo)不治本。
3、SharedPtr
template <class T>
class SharedPtr
{
public:
SharedPtr(T* ptr=NULL)
:_ptr(ptr)
,_pcount(new int(1))
{}
SharedPtr( SharedPtr<T>& t)
{
_ptr=t._ptr;
_pcount=t._pcount;
(*_pcount)++;
}
SharedPtr<T>& operator=(SharedPtr<T>& t)
{
if(_ptr!=t._ptr)
{
if(--(*_pcount)==0)
{
delete _ptr;
delete _pcount;
}
_ptr=t._ptr;
_pcount=t._pcount;
++(*p_count);
}
return *this;
}
T& operator*(){ return *_ptr; }
T* operator->(){ return _ptr; }
~SharedPtr()
{
if(--(*_pcount)==0)
{
delete _ptr;
delete _pcount;
}
}
private:
T* _ptr;
int* _pcount;
};為什么_pcount的類型是int*類型?
如果_pcount是int類型的,那么構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)和賦值運算符重載的代碼應(yīng)為:
SharedPtr(T* ptr = NULL):_ptr(ptr), _pcount(1){}
SharedPtr(SharedPtr<T>& t)
{
_ptr = t._ptr;
_pcount = t._pcount;
(_pcount)++;
}
SharedPtr<T>& operator=(SharedPtr<T>& t)
{
if (_ptr != t._ptr)
{
if (--(_pcount) == 0)
{
delete _ptr;
}
_ptr = t._ptr;
_pcount = t._pcount;
++(_pcount);
}
return *this;
}SharedPtr<int> s = new int(1); //引用計數(shù)為1 SharedPtr<int> s1(s); //引用計數(shù)為2(s的引用計數(shù)仍為1) SharedPtr<int> s2=new int (2); s=s2; cout<<*s1<<endl; //正確輸出為1,可是輸出的卻是一個隨機值,因為s的引用計數(shù)始終為1,當(dāng)運行到s=s3時,s在堆上分配的空間已經(jīng)被釋放了,所以s1指向的是被釋放后的空間。
int類型有幾個指針就會在棧上開辟幾個_pcount。而int*類型時,只在堆上開辟1塊空間來保存*_pcount的值,拷貝構(gòu)造和賦值時都用的_pcount的地址取值后進(jìn)行加減。
那為什么不用static int類型的呢?
看下面的例子:
SharedPtr<int> s1 = new int(1); SharedPtr<int> s2(s1); SharedPtr<int> s3 = new int(1); SharedPtr<int> s4(s3);
那么此時的引用計數(shù)變?yōu)榱?,應(yīng)為_pcount現(xiàn)在是所有對象共享的,定義一個對象就會+1,而我們的本意是讓s1和s2共享一個_pcount,s3和s4共享一個_pcount。而int*可以做到這一點,當(dāng)構(gòu)造s1時申請一塊空間實現(xiàn)引用計數(shù),構(gòu)造s2時引用計數(shù)+1。構(gòu)造s3時再申請一塊空間實現(xiàn)引用計數(shù),構(gòu)造s4時引用計數(shù)+1;
SharedPtr實現(xiàn)了引用計數(shù),它支持復(fù)制,復(fù)制一個SharedPtr的本質(zhì)是對這個智能指針的引用次數(shù)加1,而當(dāng)這個智能指針的引用次數(shù)降低到0的時候,該對象自動被析構(gòu)。
這樣的SharedPtr依然存在著問題。
①如果shared_ptr引用關(guān)系中出現(xiàn)一個環(huán),那么環(huán)上所述對象的引用次數(shù)都肯定不可能減為0,那么也就不會被刪除。
struct ListNode
{
int _value;
SharedPtr<ListNode> _next;
SharedPtr<ListNode> _prev;
ListNode(int x):_value(x),_next(NULL),_prev(NULL){}
};
void Test()
{
SharedPtr<Node> cur(new Node(1));
SharedPtr<Node> next(new Node(2));
cur -> _next = next;
next -> _prev = cur;
}上述例子中的對象引用計數(shù)不會減為0,所以不會調(diào)用析構(gòu),會造成內(nèi)存泄漏。
實際上,在庫函數(shù)中shared_ptr內(nèi)部實現(xiàn)的時候維護(hù)的就不是一個引用計數(shù),而是兩個引用計數(shù),一個表示strong reference,也就是用shared_ptr進(jìn)行復(fù)制的時候進(jìn)行的計數(shù),一個是weak reference,也就是用weak_ptr進(jìn)行復(fù)制的時候的計數(shù)。weak_ptr本身并不會增加strong reference的值,而strong reference降低到0,對象被自動析構(gòu),weak_ptr輔助了shared_ptr而沒有增加引用計數(shù)。因此在一個環(huán)上只要把原來的某一個shared_ptr改成weak_ptr,實質(zhì)上這個環(huán)就可以被打破了。
②模擬實現(xiàn)的SharedPtr只能用于new空間,并不能打開文件,這個時候可以用仿函數(shù)來解決這個問題。
template <class T>
struct FClose
{
void operator () (T* ptr) //重載()運算符,進(jìn)行文件指針的釋放。
{
fclose(ptr);
}
};
template <class T>
struct Delete
{
public :
void operator () (T* ptr) //重載()運算符,進(jìn)行堆上空間的釋放
{
delete ptr;
}
};
template <class T,class DEL=Delete<T>> //多傳一個參數(shù),默認(rèn)為Delete<T>,即默認(rèn)它是在堆上new出空間,需要用delete釋放
class SharedPtr
{
public:
SharedPtr(T* ptr=NULL)
:_ptr(ptr)
,_pcount(new int(1))
{}
SharedPtr( SharedPtr<T>& t)
{
_ptr=t._ptr;
_pcount=t._pcount;
(*_pcount)++;
}
SharedPtr<T>& operator=(SharedPtr<T>& t)
{
if(_ptr!=t._ptr)
{
if(--(*_pcount)==0)
{
delete _ptr;
delete _pcount;
}
_ptr=t._ptr;
_pcount=t._pcount;
++(*p_count);
}
return *this;
}
T& operator*(){ return *_ptr; }
T* operator->(){ return _ptr; }
~SharedPtr()
{
if(--(*_pcount)==0)
{
DEL()(_ptr); //釋放空間時,用DEL類型生成匿名對象調(diào)用()函數(shù)
delete _pcount;
}
}
private:
T* _ptr;
int* _pcount;
};當(dāng)需要給文件指針定義時,只用多傳一個參數(shù)就可以達(dá)到效果。
SharedPtr<FILE, FClose<FILE>> b= fopen("test.txt", "w");這樣在析構(gòu)時,就會調(diào)用FClose<FILE>()生成匿名對象,F(xiàn)Close<FILE>()(_ptr)然后調(diào)用FClose的()運算符重載函數(shù)。
標(biāo)題名稱:RAII和模擬實現(xiàn)智能指針
網(wǎng)站網(wǎng)址:http://www.chinadenli.net/article40/gccieo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供服務(wù)器托管、虛擬主機、網(wǎng)站設(shè)計公司、電子商務(wù)、定制開發(fā)、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)