以物件管理資源
假設我們使用一個用來墅模投資行為(例如股票、債券等)的程式庫,其中各種各樣的投資型別繼承自一個 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 之非正統復制行為并不適用 ” 的語境上,
最后請記住:
- 為防止資源泄露,請使用 RAII 物件,它們在建構式中獲得資源并在解構式中釋放資源,
- 兩個常被使用的 RAII classes 分別是 tr1::shared_ptr 和 auto_ptr,前者通常是較佳選擇,因為其 copy 行為比較直觀,若選擇 auto_ptr,復制動作會使它(被賦值物)指向 null,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/172818.html
標籤:其他
上一篇:Git作業原理及功能結構
下一篇:條件隨機場之淺出
