More effective c++ 條款10(下) (轉)

gugu99發表於2008-07-25
More effective c++ 條款10(下) (轉)[@more@]

條款10:在構造中防止資源洩漏(下) 

你可能已經注意到BookEntry建構函式的catch塊中的語句與在BookEntry的解構函式的語句幾乎一樣。這裡的程式碼重複是絕對不可容忍的,所以最好的方法是把通用程式碼移入一個私有helper function中,讓建構函式與解構函式都它。:namespace prefix = o ns = "urn:schemas--com::office" />

class BookEntry {


public:


  ...  // 同上


 


private:


  ...


  void cleanup();  // 通用清除程式碼


};


 


void BookEntry::cleanup()


{


  delete theImage;


  delete theAudioClip;


}


 


BookEntry::BookEntry(const string& name,


  const string& address,


     const string& imageFileName,


     const string& audioClipFileName)


: theName(name), theAddress(address),


  theImage(0), theAudioClip(0)


{


  try {


  ...  // 同上


  }


  catch (...)  {


  cleanup();  // 釋放資源


  throw;  // 傳遞異常


  }


}


 


BookEntry::~BookEntry()


{


  cleanup();


}


這就行了,但是它沒有考慮到下面這種情況。假設我們略微改動一下設計,讓theImagetheAudioClip是常量(constant)指標型別:

class BookEntry {


public:


  ...   // 同上


 


private:


  ...


  Image * const theImage;  // 指標現在是


  AudioCl* const theAudioClip;  // const型別


};

必須透過BookEntry建構函式的成員初始化表來初始化這樣的指標,因為再也沒有其它地方可以給const指標賦值(參見Effective C++條款12)。通常會這樣初始化theImage和theAudioClip:

// 一個可能在異常丟擲時導致資源洩漏的實現方法


BookEntry::BookEntry(const string& name,


  const string& address,


  const string& imageFileName,


  const string& audioClipFileName)


: theName(name), theAddress(address),


  theImage(imageFileName != ""


  ? new Image(imageFileName)


  : 0),


  theAudioClip(audioClipFileName != ""


  ? new AudioClip(audioClipFileName)


  : 0)


{}


這樣做導致我們原先一直想避免的問題重新出現:如果theAudioClip初始化時一個異常被丟擲,theImage所指的不會被釋放。而且我們不能透過在建構函式中增加try和catch 語句來解決問題,因為try和catch是語句,而成員初始化表僅允許有(這就是為什麼我們必須在 theImagetheAudioClip的初始化中使用?:以代替if-then-else的原因)。

無論如何,在異常傳遞之前完成清除工作的唯一的方法就是捕獲這些異常,所以如果我們不能在成員初始化表中放入try和catch語句,我們把它們移到其它地方。一種可能是在私有成員函式中,用這些函式返回指標,指向初始化過的theImagetheAudioClip物件。

class BookEntry {


public:


  ...  // 同上


 


private:


  ...  // 資料成員同上


 


Image * initImage(const string& imageFileName);


  AudioClip * initAudioClip(const string&


  audioClipFileName);


};


 


BookEntry::BookEntry(const string& name,


  const string& address,


  const string& imageFileName,


  const string& audioClipFileName)


: theName(name), theAddress(address),


  theImage(initImage(imageFileName)),


  theAudioClip(initAudioClip(audioClipFileName))


{}


 


// theImage 被首先初始化,所以即使這個初始化失敗也


// 不用擔心資源洩漏,這個函式不用進行異常處理。


Image * BookEntry::initImage(const string& imageFileName)


{


  if (imageFileName != "") return new Image(imageFileName);


  else return 0;


}


 


// theAudioClip被第二個初始化, 所以如果在theAudioClip


// 初始化過程中丟擲異常,它必須確保theImage的資源被釋放。


// 因此這個函式使用try...catch 。


AudioClip * BookEntry::initAudioClip(const string&


  audioClipFileName)


{


  try {


  if (audioClipFileName != "") {


  return new AudioClip(audioClipFileName);


  }


  else return 0;


  }


  catch (...)  {


  delete theImage;


  throw;


  }


}


上面的的確不錯,也解決了令我們頭疼不已的問題。不過也有缺點,在原則上應該屬於建構函式的程式碼卻分散在幾個函式里,這令我們很難維護。

更好的解決方法是採用條款9的建議,把theImagetheAudioClip指向的物件做為一個資源,被一些區域性物件管理。這個解決方法建立在這樣一個事實基礎上:theImagetheAudioClip是兩個指標,指向動態分配的物件,因此當指標消失的時候,這些物件應該被刪除。auto_ptr類就是基於這個目的而設計的。(參見條款9)因此我們把theImagetheAudioClip raw指標型別改成對應的auto_ptr型別。

class BookEntry {


public:


  ...  // 同上


 


private:


  ...


  const auto_ptr theImage;  // 它們現在是


  const auto_ptr theAudioClip;  // auto_ptr物件


};


這樣做使得BookEntry的建構函式即使在存在異常的情況下也能做到不洩漏資源,而且讓我們能夠使用成員初始化表來初始化theImagetheAudioClip,如下所示:

BookEntry::BookEntry(const string& name,


  const string& address,


  const string& imageFileName,


  const string& audioClipFileName)


: theName(name), theAddress(address),


  theImage(imageFileName != ""


  ? new Image(imageFileName)


  : 0),


  theAudioClip(audioClipFileName != ""


  ? new AudioClip(audioClipFileName)


  : 0)


{}


在這裡,如果在初始化theAudioClip時丟擲異常,theImage已經是一個被完全構造的物件,所以它能被自動刪除掉,就象theName, theAddressthePhones一樣。而且因為theImagetheAudioClip現在是包含在BookEntry中的物件,當BookEntry被刪除時它們能被自動地刪除。因此不需要手工刪除它們所指向的物件。可以這樣簡化BookEntry的解構函式:

BookEntry::~BookEntry()


{}   // nothing to do!


這表示你能完全去掉BookEntry的解構函式。

綜上所述,如果你用對應的auto_ptr物件替代指標成員變數,就可以防止建構函式在存在異常時發生資源洩漏,你也不用手工在解構函式中釋放資源,並且你還能象以前使用非const指標一樣使用const指標,給其賦值。

在物件構造中,處理各種丟擲異常的可能,是一個棘手的問題,但是auto_ptr(或者類似於auto_ptr的類)能化繁為簡。它不僅把令人不好理解的程式碼隱藏起來,而且使得程式在面對異常的情況下也能保持正常執行。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1007835/,如需轉載,請註明出處,否則將追究法律責任。

相關文章