條款13:以對象管理資源
看如下一個例子:
void f() {
Investment *pInv = createInvestment();
...
delete pInv;
}
如上,在發f()函數的結尾釋放了pInv指針所指向的內存。但是這樣的寫法是有風險的。如果…裏面的內容提前返回或者出現異常,那麼這塊內存將不能合法的釋放掉。這樣就會造成資源泄漏的問題。
那麼該條款主要說明的是,一對象管理資源,也就是讓釋放的動作放在類的析構函數中來進行,這樣在f()函數的作用域結束的時候,可以自動將內存釋放掉。
這裏主要使用的是C++標準庫中的智能指針來管理內存資源。
void f() {
std::auto_ptr<Investment> pInv(createInvestment());
}
使用std::auto_ptr來管理資源,這裏提出了兩個關鍵想法:
- 獲得資源後立刻放進管理對象內。
- 管理對象運用析構函數確保資源被釋放。
也就是說,在createInvestment()函數被調用後,獲取的資源需要立即放入到std::auto_ptr資源管理對象中,同時在執行函數的作用域結束的時候,調用資源管理對象的析構函數來釋放資源。
std::auto_ptr不能與其他被複制的指針擁有同一個對象,也就是如果出現:
std::auto_ptr<Investment> pInv1 = pInv;
那麼pInv所指向的對象將未null。當然std也提供的共享指針:std:shared_ptr。也就是如果使用std:shared_ptr,那麼pInv1和pInv所指向的對象是同一個內存。
在實際開發中,我們經常會使用std:shared_ptr,但本人不是太喜歡使用共享智能指針。有時候如果我們要控制釋放資源的時序或這時機時,共享智能指針顯然是不合適的。所以使用它也是視情況而定,它的出現減少的程序員對內存管理的工作。但是在自己管理資源時,會遵循RAII原則。
總結
爲防止資源泄露,請使用RAII對象,它們在構造函數中獲得資源並在析構函數中釋放資源。
兩個常被使用的RAII classes分別是std::shared_ptr和auto_ptr。前者通常是最佳選擇,因爲其copy行爲比較直觀。若選擇auto_ptr,複製動作會使它(被複制物)指向null。
條款14:在資源管理類中小心copying行爲
在條款13中有說到本人不喜歡使用智能指針,那麼如果是自己管理類對象的時候,也要自定義RAII類,來對資源進行管理。那麼自定義RAII類需要主要什麼呢?
條款14說明,在自定義RAII類的時候需要小心copying行爲,因爲如果不對其採取措施的話,會出現野指針的使用或者一些其他不明確的行爲發生。
所以一般採取的措施如下:
- 禁止複製。
- 對底層資源祭出“引用計數法”(reference-count)。類似std:shared_ptr。
- 複製底部資源。(對內部的所以資源進行深拷貝)
- 轉移底部資源的擁有權。類似std::auto_ptr
其中1方法使用的比較多,通常在開發過程中,很多業務類是不需要被複制的,所以乾脆禁止其複製就可以了。同樣的3也會經常使用,這適用於一些小類,容易複製的簡單類,大多是數據協議類。如果出現2和4的情況,可以直接使用std:shared_ptr或者std::auto_ptr,這樣大大減少了開發量。
總結
複製RAII對象必須一併複製它所管理的資源,所以資源的copying行爲決定RAII對象的copying行爲。
普通而常見的RAII class copying行爲是:抑制copying、施行引用計數法。不過其他行爲也都可能被實現。
條款15:在資源管理類中提供原始資源的訪問
使用過shared_ptr應該都清楚,它提供了一個get方法用於客戶獲取它的原始指針。即提供了客戶對原始資源的訪問。
std::shared_ptr<font> ptr = std::make_shared<font>();
font* p = ptr.get();
該條款告訴我們的是對於RAII類我們需要對其提供一個原始資源的訪問方式。顯式或者隱式訪問都可以。因爲有時候會出現這種情況:
int daysHeld(const Investment* pi);
如上的方法,他需要的形參就是原始指針的類型,那麼如果使用智能指針shared_ptr作爲實參進行傳遞是不行的。所以標準庫爲我們提供了get方法用於獲取原始資源的訪問權。
總結
APIs往往要求訪問原始資源,所以每一個RAII class應該提供一個“取得其所管理之資源”的辦法。
對原始資源的訪問可能經由顯示轉換或隱式轉換,一般而言顯示轉換比較安全,但是隱式轉換對客戶比較方便。
條款16:成對使用new和delete時要採取相同形式
該條款比較簡單,它要求我們在使用new和delete時要採取相同的形式。如果出現如下的情況,那麼會出現未定義的問題:
std::string *p = new std::string;
delete []p;
std::string *p1[2] = new std::string[2];
delete p1;
如上,如果只是申請了一個對象,但是使用delete[],就會出現程序多次進行析構。如果申請的是數組對象,而使用的delete,那麼程序可能只釋放了一次內存,調用了一次析構函數而已。
總結
如果你在new表達式中使用[],必須在相應的delete表達式中也使用[],如果你在new表達式中不使用[],一定不要在相應的delete表達式中使用[]
條款17:以獨立語句將newed對象置入智能指針
考慮如下的情況:
processWidget(std::shared_ptr<widget> pw, int priority);
processWidget(std::shared::ptr<widget>(new widget), priority());
如上的調用可能會存在內存泄漏的風險。我們知道程序執行的順序:
- new widget
- 調用std::shared::ptr的構造函數。
但是這個函數由第二個參數,所以程序執行的順序可能是這樣的:- new widget
- priority()
3.調用std::shared::ptr的構造函數
也就是 priority()的調用插在了中間,那麼當 priority()出現異常的話,new widget的對象將無法捕捉到,這會導致我們無法使用std::shared::ptr對該內存進行管理。從而導致內存泄漏。
那麼正確的做法是什麼樣的呢:
std::shared_ptr<widget> pw(new widget);
processWidget(pw, priority());
這樣保證了程序的執行順序,從而防止出現內存泄漏的風險。
總結
以獨立的語句將newed對象存儲於(置入)智能指針內。如果不這樣做,一旦異常被拋出,有可能導致難以察覺的資源泄漏。