假設我們使用一個用來墅模投資行為(例如股票、債券等)的程式庫,其中各種各樣的投資型別繼承自一個 root class Investment:
class Investment { ... }; // root class
進一步假設,這個程式庫通過一個工廠函數(factory function,見條款07)供應我們特定的 Investment 物件:
Investment* createInvestment(); // 返回指標,指向Investment 繼承體系內的動態分配物件。呼叫者有責任刪除它
如上面註釋所言,createInvestment 的呼叫端使用了函數返回的物件後,有責任刪除它。現在考慮有個 f 函數來履行這個責任:
void f(){
Investment* pInv = createInvestment(); // 呼叫 factory 函數
...
delete pInv; // 釋放 pInv 所指物件
}
這樣看起來妥當,但若干情況下 f 可能無法刪除它得自 createInvestment 的投資物件:
① 「 … 」 區域內的一個過早的 return 語句結束了函數。
② delete 語句用於迴圈內,但是遇到了 continue 或 goto 語句過早退出。
③ 「 … 」 區域內的語句丟擲異常,控制流將不會再降臨 delete。
當然,謹慎地編寫程式可以防止這類錯誤,但是隨著時間漸漸過去程式碼可能被修改。一但軟體開始接受維護,可能會有某些人加入了 return 或 continue 而導致了資源記憶體洩露等問題是行不通的。
為確保 createInvestment 返回的資源總是被釋放,我們需要將資源放進物件內,當控制流離開 f ,該物件的解構函式會自動釋放那些資源。
許多資源被動態分配於 heap 內而後被用於單一區塊或函數內。它們應該在控制流離開那個區塊或函數時被釋放。
標準的程式庫提供的 auto_ptr 正是針對這種形勢而設計的特質產品。
auto_ptr 是個 「 類指標(pointer-like)物件 」,也就是所謂的 「 智慧指標 」,其解構函式自動對其所指物件呼叫 delete。下面示範如何使用 auto_ptr 以避免 f 函數潛在的資源洩露可能性。
void f(){
std::auto_ptr<Investment> pInv(createInvestment()); // 呼叫 factory 函數,一如既往使用 pInv
// 經由 auto_ptr 的解構函式自動刪除 pInv
...
}
這個簡單的例子示範了 「 以物件管理資源 」 的兩個關鍵想法:
實際上 「 以物件管理資源 」 的觀念常被稱為 「 資源取得時機便是初始化時機 」 (Resource Acquisition Is Initialization;RAII)。
由於 auto_ptr 被銷燬時會自動刪除它所指之物,所以一定要注意別讓多個 auto_ptr 同時指向同一物件。如果真那樣,物件會被刪除一次以上,這會使你的程式搭上駛向 「 未定義行為 」 的快速列車上。為了預防這個問題,auto_ptrs 有一個不同尋常的性質:若通過 copy 建構函式或 copy assignment 操作符複製它們,它們會變成 null,而複製所得的指標將取得資源的唯一擁有權!
std::auto_ptr<Investment> pInv1(createInvestment()); // pInv1 指向 createInvestment 返回物
std::auto_ptr<Investment> pInv2(pInv1); // 現在 pInv2 指向物件,pInv1 被設為 null
pInv1 = pInv2; // 現在 pInv1 指向物件,pInv2 被設為 null
這一詭異的複製行為,附加上其底層條件:「 受 auto_ptrs 管理的資源必須絕對沒有一個以上的 auto_ptr 同時指向它 」,意味 auto_ptrs 並非管理動態分配資源的神兵利器。
舉個栗子,STL 容器要求其元素髮揮 「 正常的 」 複製行為,因此這些容器容不得 auto_ptr。
auto_ptr 的替代方案是 「 參照計數型智慧指標 」 (PCSP)。所謂 PCSP 的功能就是持續追蹤共有多少物件指向某筆資源,並在無人指向它時自動刪除該資源。但它不能打破環狀參照,例如兩個其實已經沒有使用的物件彼此互指,因而好像還處於 「 被使用 」 狀態。
TR1 的 tr1::shared_ptr(見條款54)就是個PCSP,所以可以改造一下 f :
void f(){
...
std::tr1::shared_ptr<Investment> pInv(createInvestment()); // 呼叫 factory 函數,使用 pInv 一如既往
// 經由 shared_ptr 解構函式自動刪除 pInv
...
}
這段程式碼看起來和使用 auto_ptr 的那個版本幾乎相同,但是 shared_ptr 的複製行為就正常多了:
void f(){
...
std::tr1::shared_ptr<Investment> pInv1(createInvestment()); // pInv1 指向 createInvestment 返回物
std::tr1::shared_ptr<Investment> pInv2(pInv1); // pInv1 和pInv2 指向同一個物件
pInv1 = pInv2; // 無變化
... // pInv1 和 pInv2 被銷燬,它們所指的物件也被自動銷燬
}
由於 tr1::shared_ptrs 的複製行為 「 一如預期 」,它們可被用於 STL 容器以及其他 「 auto_ptr 之非正統複製行為並不適用 」 的語境上。
最後請記住: