天天看點

條款13(一):以對象管理資源條款13:以對象管理資源

條款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。