RAII技術(資源獲取即初始化——Resource Acquisition Is Initialization)
編寫程序我們經常會使用new或者mallco來向系統申請內存,但我們也會可能忘記使用delete去釋放他們,而這種不釋放手動申請的資源的後果是十分嚴重(內存溢出 memory overflow)。這時候RAII技術應運而生。
RAII慣用法是在Bjarne Stroustrup的《C++程序設計語言(第3版)》一書中有講到:
使用局部對象管理資源的技術通常稱爲“資源獲取就是初始化”。這種通用技術依賴於構造函數和析構函數的性質以及它們與異常處理的交互作用
總結RAII技術就類似以下幾點:
- 將將要申請的資源封裝到類內(構造函數&析構函數)。
- 通過類的本地實例使用資源(類的對象)
- 當對象超出作用域(scope)時,資源將自動釋放(系統自動調用析構函數)。
這保證了無論在資源使用期間發生什麼,它最終都會被釋放(不管是由於正常返回、銷燬包含的對象,還是引發異常)。
RAII的本質內容是用對象代表資源,把管理資源的任務轉化爲管理對象的任務,將資源的獲取和釋放與對象的構造和析構對應起來,從而確保在對象的生存期內資源始終有效,對象銷燬時資源必被釋放;這種技術還能令代碼更加簡潔(如果手動申請了大量的資源,無窮無盡的delete語句着實令人頭疼)。
例子
void Func()
{
int *resource= new int[10];
...//do something
...//do something
delete[] resource;//釋放內存防止內存溢出
}
如同標準做法,在申請了resource內存資源後,我們必須要對他進行釋放。
但是凡事都有例外,可能在函數內發生了一些小插曲:
void Func()
{
int *resource= new int[10];
...//do something
if(something_wrong_happen)
return;
...//do something
delete[] resource;//釋放內存防止內存溢出
}
我們在未釋放掉resource的情況下就結束了函數(或者是程序拋出了異常導致函數結束)。
要注意的是,和申請的局部變量不同,函數中使用了new而不delete,通常是不會釋放的空間的:內存被new出來以後,有一個指針指向這個空間。malloc 或者 new申請的空間是在"堆"上的,平時我們都是用聲明變量來申請空間的,此時申請到的空間是"棧"上的。
不管是在子函數中,或是在主函數中,都必須有一次delete來釋放這個空間,如果沒有做delete,即使退出程序,也無法回收這段內存了,內存就被泄露了。
又或者我們有這樣的代碼:
void Func()
{
int *resource1= new int[10];
int *resource2= new int[9];
int *resource3= new int[666];
//.........
...//申請了成噸的資源空間
delete[] resource3;
delete[] resource2;
delete[] resource1;
//.........
...//根據需要釋放某些內存(或者全部釋放)
}
如果績效根據代碼量來計算(什麼破公司!),那麼這樣的代碼確實很有用。但是這看起來十分的令人頭疼,畢竟實際情況指針變量名可不是123這樣排序的。
這時候我們可以利用RAII技術——將將要申請的資源封裝在類內:
template<class sizetype>
class Resource {
public:
Resource(size_t sz):size(sz) {
data = new sizetype[sz];
}
void create() {
for (int i = 0; i < size; i++) {
*(data + i) = i;
}
}
void show() {
for (int i = 0; i < size; i++) {
cout<<data[i]<<" ";
}
cout << endl;
}
~Resource() {
while (data) {
delete[] data;
data = NULL;
//重要,爲了安全,將data置NULL是爲了防止以後的程序使用它,
//防止delete以後可能出現的非法訪問(野指針)
//或者我們可以用share_ptr來代替他
}
}
private:
// 禁止拷貝操作(拷貝構造和拷貝賦值都應該禁止)
Resource (Resource const&);
Resource& operator = (Resource const&) ;
protected:
sizetype * data;
size_t size;
};
我們通常會禁止類對象之間進行相互拷貝或者拷貝構造,因爲此類的對象代表一種資源,不具有複製的意義存在。
在運用了RAII之後,我們就不再害怕函數內發生錯誤而導致提前的結束了。
使用我們的Resource類來管理資源
int main()
{
Resource<int>res(3);
res.create();
res.show();
return 0;
}
至此,我們的資源已經如同局部對象一樣:自動申請,自動釋放。
同樣的方法我們也可以運用到對文件的操作:fopen(),fclose()上 或者 scope lock (局部鎖技術)等等對內存的把握也一樣嚴格的技術上,RAII會幫到我們許多。