天天看點

3、資源管理

條款 13: 以對象資源管理

在以對象管理資源的理念中,關鍵的兩個想法就是:(1)獲得資源後立即放入管理對象内;實際上“以對象管理資源”的觀念常被稱為“資源取得時機就是初始化時機”(Resource Acquisition Is Initialization; RAII),因為我們幾乎總是可以在獲得一筆資源後于同一語句内以它初始化某個管理對象,也可能是指派某個對象。(2)管理對象運用析構函數確定資源被釋放;不論控制流如何離開區塊,一旦對象被銷毀(例如當對象離開作用域)其析構函數自然會自動調用,于是資源被釋放。通常可以由auto_ptr和shared_ptr達到目的,但是二者是不一樣的。auto_ptr被銷毀時會自動删除它所指向之物,是以一定特别注意别讓多個auto_ptr同時指向同一個對象。auto_ptr的不尋常性質:若通過copy構造函數或者copy assignment從操作符複制他們,它們會變成null,而複制所得的指針将取得資源的唯一擁有權!

std::auto_ptr<Investment> pInv1(CreatInvestment());  //pInv1指向CreatInvestment的傳回值
std::auto_ptr<Investment> pInv2(pInv1);  //現在pInv2指向對象,pInv1被設為null
pInv1 = pInv2;  //現在pInv1指向對象,pInv2被設為null
           

這種詭異的複制行為機上底層條件:“受auto_ptr管理的資源必須絕對沒有一個以上的auto_ptr同時指向它”,意味着auto_ptr并非是管理動态配置設定資源的神兵利器。auto_ptr的替代方案就是“引用記數型智慧指針(reference-counting smart pointer, RCSP)”,它能持續最總共有多少個對象指向某筆資源,并在無人指向它的時候自動删除資源。由于auto_ptr和shared_ptr二者的析構函數被做的是delete而不是delete [],那麼在動态配置設定的array中不要使用二者。

請記住:

1、為了防止資源洩漏,請使用RAII(Resource Acquisition Is Initialization)對象,在構造函數中獲得資源并在析構函數中釋放資源;

2、兩個被常用的RAII classes分别是shared_ptr和auto_ptr,前者通常是較佳選擇,因為其copy行為比較直覺正常;後者copy會使它指向null。

條款 14:在資源管理類中小心copying行為

作為資源管理類,在資源獲得時機就是初始化時機。如果RAII對象被指派或者複制,那就會發生兩個資源管理對象指向同一份資源。為了解決這一問題,可以有以下選擇:

1)禁止複制。可以将copying行為的操作定義成private;2)采用引用計數法。類似于shared_ptr,保留資源直到最優一個對象被銷毀;3)深拷貝。重寫拷貝構造函數以及指派操作符進行深度拷貝;4)轉移資源的擁有權。類似于auto_ptr,確定永遠隻有一個對像擁有該資源,在對象複制的同時對資源的擁有權從被複制物轉移到目标物。

請記住:

1、指派RAII對象必須一并複制它所管理的資源,是以資源的copying行為決定了RAII對象的copying行為;

2、普遍常見的ARII類copying行為是:禁止複制、引用計數。不過其他行為也都可能被實作。

條款 15:在資源管理類中提供對原始資源的通路

資源管理類在防止資源洩漏的有效措施,但是許多API直接涉及資源,那麼必須繞過資源管理對象直接通路原始資源(raw resource)。例如:在條款13中提到的使用智能指針如auto_ptr或者shared_ptr儲存factory函數如createInvestment的結果:

std::tr1::shared_ptr<Invectment> pInv(createInvestment());
假設希望某個函數能夠處理Investment對象,像這樣:
int daysHeld(const Investment* pi); //傳回投資天數
那麼調用的時候:
int days = daysHeld(pInv);  //錯誤!
這樣是通不過編譯的,因為daysHeld需要的是Investment*指針,而傳進去的參數卻是std::tr1::shared_ptr<Invectment>的對象
           

這就需要一個函數蔣RAII對象轉換成其所包含的原始資源(本例為Investment*),通常有兩種做法:隐式轉換和顯式轉換。tr1::shared_ptr和auto_ptr都提供一個get成員函數,用來執行顯式轉換,它會傳回智能指針内部的原始指針。

請記住:

1、APIs往往是要求通路原始資源,是以每個RAII類應該提供一個“取得其所管資源”的方法;

2、對原始資源的通路可能經由顯式轉換或隐式轉換。一般顯式轉換比較安全,但隐式轉換對客戶比較友善。

條款 16:成對使用new和delete時要采取相同形式

規則很簡單:如果你用new時使用[],那麼對應調用delete時也使用[];如果你用new時沒有使用[],那麼對應調用delete時也不要使用[]。注意typedef時:

typedef std::string AddressLines[4];
std::string* pa1 = new AddressLines;
那麼在delete時:
delete[] pa1;  //正确!
delete pa1;   //行為未定義!
           

是以為了避免類似的錯誤發生,最好盡量不要對數組形式做typedef動作。

請記住:

1、如果你用new時使用[],那麼對應調用delete時也使用[];如果你用new時沒有使用[],那麼對應調用delete時也不要使用[]。

條款 17:以獨立語句将newed對象置入智能指針

假設存在以下視窗優先級處理函數processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());第一個實參由兩部分組成:執行“new Widget”表達式和調用tr1::shared_ptr構造函數。是以在調用processWidget之前編譯器必須建立代碼,做以下三件事:調用priority、執行“new Widget”和調用tr1::shared_ptr構造函數。但是執行順序可能是這樣:1)執行“new Widget”;2)調用priority;3)調用tr1::shared_ptr構造函數。如果在priority調用中出現異常,那麼new Widget傳回的指針将會遺失,智能指針無法對其進行資源釋放進而引發資源洩漏。避免這類問題的辦法很簡單:使用語句分離,分别寫出1)建立Widget并将其置入隻能指針内;2)把隻能指針傳給processWidget:

std::tr1::shared_ptr<Widget> pw(new Widget);
processWidget(pw,priority());
           

編譯器沒有對跨語句的各項操作進行重新排列的自由,是以以上方法行得通。

請記住:

1、以獨立語句将newed對象置入隻能指針内,如果不這樣做,一旦異常抛出,有可能導緻難以察覺的資源洩漏。

繼續閱讀