[C++] 以對象管理資源

所謂資源就是,一旦用了它,將來必須還給系統(tǒng)。如果不這樣,糟糕的事情就會發(fā)生。
C++程序中最常使用的資源就是動態(tài)分配內(nèi)存(如果你分配內(nèi)存卻從來不曾歸還它,會導致內(nèi)存泄露),
但內(nèi)存只是你必須管理的眾多資源之一。

其他常見的資源還包括文件描述符(file description),互斥鎖(mutex lock),圖形界面中的字型和筆刷,數(shù)據(jù)庫連接,以及網(wǎng)絡socket。
不論哪一種資源,最重要的是,當你不再使用它時,必須將它還給系統(tǒng)。

嘗試在任何運用情況下都確保以上所言,是件困難的事,但當你考慮到異常,函數(shù)內(nèi)多重回傳路徑,程序維護員改動軟件卻沒能充分理解隨之而來的沖擊,
態(tài)勢就很明顯了,資源管理的特殊手段還不很充分夠用。

1. delete

假設我們使用一個用來塑模投資行為(例如,股票,債券等等)的程序庫,
其中各式各樣的投資類型繼承自一個root class Investment

// “投資類型”集成體系中的root class
class Investment { ... };

進一步假設,這個程序庫通過一個工廠函數(shù)供應我們特定的Investment對象:

// 返回指針,指向Investment繼承體系內(nèi)的動態(tài)分配對象
// 調(diào)用者有責任刪除它
Investment* createInvestment();

createInvestment的調(diào)用端,使用了函數(shù)返回的對象后,有責任刪除之。
現(xiàn)在考慮有個f函數(shù)履行了這個責任。

void f(){

    // 調(diào)用factory函數(shù)
    Investment* pInv = createInvestment();

    ...

    // 釋放pInv所指對象
    delete pInv;
}

這看起來妥當,但若干情況下,f可能無法刪除它得自createInvestment的投資對象。
或許因為“...”區(qū)域內(nèi)的一個過早的return語句,如果這樣一個return被執(zhí)行起來,控制流就絕不會觸及delete語句。
類似情況發(fā)生在對createInvestment的使用及delete動作位于某循環(huán)內(nèi),而該循環(huán)由于某個continue或goto語句過早退出。
最后一種可能是“...”區(qū)域內(nèi)的語句拋出異常,果真如此控制流將再次不會幸臨delete。

無論delete如何被略過去,我們泄漏的不只是內(nèi)含投資對象的那塊內(nèi)存,
還包括那些投資對象所保存的任何資源。

當然啦,謹慎的編寫程序可以防止這一類錯誤,但你必須想想,
代碼可能會在時間漸漸過去后被修改,一旦軟件開始接受維護,可能會有某些人添加return語句或continue語句而未能全然領悟它對函數(shù)的資源管理策略造成的后果。
更糟的是f的“...”區(qū)域有可能調(diào)用一個“過去從未拋出異常,卻在被‘改善’之后開始那么做”的函數(shù),
因此單純倚賴“f總是會執(zhí)行其delete語句”是行不通的。

2. 資源管理對象

為確保createInvestment返回的資源總是被釋放,我們需要將資源放進對象內(nèi),
當控制流離開f,該對象的析構(gòu)函數(shù)會自動釋放那些資源。
把資源放進對象內(nèi),我們便可倚賴C++的“析構(gòu)函數(shù)自動調(diào)用機制”確保資源被釋放。

許多資源被動態(tài)分配與heap內(nèi)而后被用于單一區(qū)塊或函數(shù)內(nèi),它們應該在控制流離開那個區(qū)塊或函數(shù)時被釋放。
標準程序庫提供的auto_ptr正是針對這種形勢而設計的特制產(chǎn)品。
auto_ptr是個“類指針(pointer-like)對象”,也就是所謂“智能指針”,其析構(gòu)函數(shù)自動對其所指對象調(diào)用delete。
下面示范如何使用auto_ptr以避免f函數(shù)潛在的資源泄漏可能性:

void f(){

    // 調(diào)用factory函數(shù)
    std::auto_ptr<Investment> pInv(createInvestment());

    // 一如既往的使用pInv
    ...

    // 經(jīng)由auto_ptr的析構(gòu)函數(shù)自動刪除pInv
}

實際上“以對象管理資源”的觀念常被稱為“資源取得時機便是初始化時機”
(Resource Acquisition Is Initialization,RAII),
因為我們幾乎總是在獲得一筆資源后于同一語句內(nèi)以它初始化某個管理對象。
有時候獲得的資源被拿來賦值(而非初始化)某個管理對象,
但不論哪一種做法,每一筆資源都在獲得的同時立刻被放進管理對象中。

不論控制流如何離開區(qū)塊,一旦對象被銷毀(例如當對象離開作用域),其析構(gòu)函數(shù)自然會被自動調(diào)用,與時資源被釋放。
如果資源釋放動作可能導致拋出異常,事情變得有點棘手。(見條款8 P44

3. auto_ptr和shared_ptr

由于auto_ptr被銷毀時會自動刪除它所指之物,所以一定要注意別讓多個auto_ptr同時指向同一對象。
如果真是這樣,對象會被刪除一次以上,而那會使你的程序搭上“未定義行為”的快速列車上。
為了預防這個問題,auto_ptr有一個不尋常的性質(zhì),
若通過copy構(gòu)造函數(shù)或copy assignment操作符復制它們,它們會變成null,而復制所得的指針將取得資源的唯一擁有權(quán)。

// pInv1指向createInvestment返回物
std::auto_ptr<Investment> pInv1(createInvestment());

// copy構(gòu)造函數(shù),現(xiàn)在pInv2指向?qū)ο?,pInv1被設為null
std::auto_ptr<Investment> pInv2(pInv1);

// copy assignment操作符,現(xiàn)在pInv1指向?qū)ο?,pInv2被設為null
pInv1 = pInv2 ;

這一詭異的復制行為,附加上其底層條件:“受auto_ptr管理的資源必須絕對沒有一個以上的auto_ptr同時指向它”,
意味著auto_ptr并非管理動態(tài)分配資源的神兵利器。
舉個例子,STL容器要求其元素發(fā)揮“正常的”復制行為,因此這些容器容不得auto_ptr

auto_ptr的替代方案是“引用計數(shù)型智慧指針”(reference-counting smart pointer,RCSP),
所謂RCSP也是個智能指針,持續(xù)追蹤共有多少對象指向某筆資源,并在無人指向它時自動刪除該資源。
RCSP提供的行為類似垃圾回收,不同的是RCSP無法打破環(huán)狀引用(cycles of reference),
例如,兩個其實已經(jīng)沒被使用的對象彼此互指,因此好像還處在“被使用”狀態(tài)。

TR1的tr1::shared_ptr就是個RCSP,所以你可以這么寫f

void f(){    
    // 調(diào)用factory函數(shù)
    std::tr1::shared_ptr<Investment> pInv(createInvestment());

    // 使用pInv一如既往
    ...

    // 經(jīng)由shared_ptr析構(gòu)函數(shù)自動刪除pInv
}

這段代碼看起來幾乎和使用auto_ptr的那個版本相同,但shared_ptr的復制行為正常多了。

4. delete[]

auto_ptrtr1::shared_ptr兩者都在其析構(gòu)函數(shù)內(nèi)做delete而不是delete[]動作。
那意味著在動態(tài)分配而得的array身上使用auto_ptrtr1::shared_ptr是個餿主意。
盡管如此,可嘆的是,那么做仍能通過編譯。

// 餿主意,會用上錯誤的delete形式
std::auto_ptr<std::string> aps(new std::string[10]);

// 相同問題
std::tr1::shared_ptr<int> spi(new int[1024]);

你或許會驚訝的發(fā)現(xiàn),并沒有特別針對“C++動態(tài)分配數(shù)組”而設計的類似auto_ptrtr1::shared_ptr那樣的東西,甚至TR1中也沒有。
那是因為vector和string幾乎總是可以取代動態(tài)分配而得的數(shù)組。
如果你還是認為擁有針對數(shù)組而設計,類似auto_ptrtr1::shared_ptr那樣的class較好,看看Boost吧。
在那你會很高興的發(fā)現(xiàn)boost::scoped_arrayboost::shared_array class,它們都提供你要的行為。


Effective C++ - P61

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容