Effective C++ 之《資源管理》

條款13:以對象管理資源

  看如下一個例子:

void f() {
	Investment *pInv = createInvestment();
	...
	delete pInv;
}

  如上,在發f()函數的結尾釋放了pInv指針所指向的內存。但是這樣的寫法是有風險的。如果…裏面的內容提前返回或者出現異常,那麼這塊內存將不能合法的釋放掉。這樣就會造成資源泄漏的問題。

  那麼該條款主要說明的是,一對象管理資源,也就是讓釋放的動作放在類的析構函數中來進行,這樣在f()函數的作用域結束的時候,可以自動將內存釋放掉。

  這裏主要使用的是C++標準庫中的智能指針來管理內存資源。

void f() {
	std::auto_ptr<Investment> pInv(createInvestment());
}

  使用std::auto_ptr來管理資源,這裏提出了兩個關鍵想法:

  1. 獲得資源後立刻放進管理對象內。
  2. 管理對象運用析構函數確保資源被釋放。

  也就是說,在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行爲,因爲如果不對其採取措施的話,會出現野指針的使用或者一些其他不明確的行爲發生。

  所以一般採取的措施如下:

  1. 禁止複製。
  2. 對底層資源祭出“引用計數法”(reference-count)。類似std:shared_ptr。
  3. 複製底部資源。(對內部的所以資源進行深拷貝)
  4. 轉移底部資源的擁有權。類似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());

  如上的調用可能會存在內存泄漏的風險。我們知道程序執行的順序:

  1. new widget
  2. 調用std::shared::ptr的構造函數。
      但是這個函數由第二個參數,所以程序執行的順序可能是這樣的:
  3. new widget
  4. priority()
    3.調用std::shared::ptr的構造函數

  也就是 priority()的調用插在了中間,那麼當 priority()出現異常的話,new widget的對象將無法捕捉到,這會導致我們無法使用std::shared::ptr對該內存進行管理。從而導致內存泄漏。
  那麼正確的做法是什麼樣的呢:

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

  這樣保證了程序的執行順序,從而防止出現內存泄漏的風險。

總結

以獨立的語句將newed對象存儲於(置入)智能指針內。如果不這樣做,一旦異常被拋出,有可能導致難以察覺的資源泄漏。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章