條款13:以對象管理資源
Use objects to manage resources.
本章分為兩個部分。
資源
所謂資源(resources),指的就是:
- 如果使用了它,将來一定要還給系統。如果不還給系統,就會發送糟糕的事情。
C++程式内,最常用的資源就是動态配置設定記憶體(如果配置設定的記憶體不歸還給系統,就會導緻記憶體洩漏),但是,記憶體隻是需要進行管理的衆多資源之一。其他比較常見的資源還有:
- 檔案描述器(file descriptors)
- 互斥鎖(mutex locks)
- 圖形界面中的字型和筆刷
- 資料庫連接配接
- 網絡sockets
舉個例子:
假設我們使用一個用來模拟投資行為的程式庫(例如模拟股票、債券等等),其中各式各樣的投資類型則繼承自一個root class Investment:
class Investment { ... } //“投資類型”繼承體系中的 root class
對于這個程式,程式庫通過一個工廠函數(factory function),條款7供應特定的Investment對象:
Factory函數會“傳回一個base class指針, 指向新生成的derived class對象”。
Investment* createInvestment(); //傳回指針,指向Investment繼承體系内的動态配置設定對象。
//調用者有責任删除它。這裡為了簡化,刻意不寫參數。
就像上面所說,createInvestment的調用端在使用了函數傳回的對象後,有責任将其删除。定義一個實作這個責任的函數f:
void f()
{
Investment* createInvestment(); //調用factory函數
...
delete pInv; //釋放pInv所指對象
}
上面的代碼中,雖然開起來是可行的,但是存在着諸多情況下,f無法删除它從createInvestment的投資對象:
- 原因之一可能是在“ … ”内的代碼可能存在過早的return語句。
如果這樣的return執行起來,控制流就絕不會觸及delete語句。
- 另一種的原因則是“ … ”内的語句抛出異常。
此時控制流也不會觸及delete語句。
- 總之,無論delete語句因為什麼原因被忽略了,所洩漏的不僅是内含投資對象的記憶體,還有投資對象所儲存的所有資源。
是以,為了確定createInvestment傳回的資源總是被釋放,需要将資源放入到對象内。當控制流離開f,該對象的析構函數會自動釋放這些資源。實際上,換句話說:
- 将資源放到對象内,便可以依賴C++的“析構函數自動調用機制”確定資源被順利釋放。
auto_ptr
許多資源被動态配置設定在heap中而後用于單一區塊或函數内。對于這些資源:
- 它們應該在控制流離開這個區塊或函數時完成釋放。
對于這一點,标準程式庫提供了auto_ptr來實作這個目标。
- auto_ptr是一個“類指針(pointer-like)對象”,也就是所謂的“智能指針”,其析構函數自動對其所指對象調用delete。
auto_ptr的使用:
void f()
{
std::auto_ptr<Investment> pInv(createInvestment()); //調用factory函數,使用pInv
... //經由auto_ptr的析構函數會自動删除pInv
}
RAII
由上面的例子,關于“以對象管理對象”的:
1,獲得資源後立即放入到管理對象(managing object)之中。在上面的代碼中,createInvestment傳回的資源被當做它的管理者auto_ptr的初值。實際上,“以對象管理資源”通常被稱為:
- 資源獲得時機,便是初始化時機。(Resource Acquisition Is Initialization, RAII)
因為我們幾乎總是在獲得一筆資源後于同一語句内,用它初始化某個管理對象。另外,有的時候獲得的資源被用來指派(而非初始化)某個管理對象。不管是初始化還是指派,每一筆資源都是在獲得的同時立刻被放進管理對象之中。
2,管理對象(managing object)運用析構函數確定資源被釋放。不管控制流是如何離開區塊或函數,一旦對象被銷毀了(例如當對象離開作用域),其析構函數就自動會被調用,于是資源就被釋放了。如果資源釋放的動作可能會抛出異常,可以參考條款8中的介紹。
由于auto_ptr被銷毀時會自動删除它所指的東西,是以一定要注意,不要讓多個auto_ptr同時指向同一個對象。如果對象被删除了一次一上,就會出現“未定義行為”的問題。
針對這個問題,auto_ptr有這樣的一個特性來預防這一問題:
- 若是想要通過copy構造函數或者copy assignment操作符去複制它們,它們就會變成null,而複制所得的指針将會獲得資源的唯一擁有權。
std::auto_ptr<Investment> pInv1(createInvestment()); //pInv1指向createInvestment傳回物
std::auto_ptr<Investment> pInv2(pInv1); //(copy構造)現在pInv2指向這個對象,pInv1被設為null
pInv1 = pInv2; //(copy assignment)現在有變成了pInv1指向這個對象,pInv2被設為null
對于上面這種奇怪的複制行為,其底層的邏輯為:
- 受auto_ptr管理的資源必須絕對沒有一個以上的auto_ptr同時指向它。
因為這一特性,auto_ptr并不是管理動态配置設定資源的最好的選擇。例如,STL容器就要求其元素要有“正常的”複制行為,是以這些容器就無法使用auto_ptr。