7 More Effective C++—條款10(建構函式內阻止記憶體洩漏)
阿新 • • 發佈:2018-12-16
1 提出問題
上一篇文章中,我們討論瞭如下情況,當函式doSomething()被呼叫時,heap中資源無法被釋放,導致記憶體洩漏問題發生。
void function() {
MyObject *object = new MyObject;
object->doSomething();
delete object;
}
本篇中,我們將討論這樣一種情況:當類中需要“包含多個heap物件“,但是在建構函式中卻出現異常的情況下,如何釋放掉已經建立的heap物件。
考慮如下情景。電話簿中需要包含如下資訊:姓名,號碼,頭像,一段特定響鈴音樂。可以定義如下類。
class BookEntry { public: BookEntry(const string &name, const string& address, const string& image, const string& audio); ~BookEntry(); private: string m_name, m_address; std::list<PhoneNumber> m_number; Image* m_photo; Audio *m_audio; } BookEntry::BookEntry(const string &name, const string& address, const string& image, const string& audio) { if (image != "") { m_image = new Image(image); } if (audio != "") { m_audio = new Audio(audio); } } BookEntry::~BookEntry() { delete m_image; // C++允許空指標被delete,不會丟擲異常 delete m_audio; }
上面程式碼中,若在執行new Audio(audio)語句中出現異常,由於類並沒有建立成功,解構函式不會被呼叫,因此m_image指向的記憶體就會出現記憶體洩漏。所以,下面的程式碼不會執行。
void testBookEntry() { BookEntry entry = 0; try { // 建構函式丟擲異常,m_image指向heap記憶體可能會洩漏 // entry可能為null指標 entry = new BookEntry("Danny", "Beijing", "photo.jpg", "voic.mp3"); ... } catch (...) { delete entry;// 不會丟擲異常,但是m_image指向記憶體依然會洩漏 throw; } delete entry; // 不會丟擲異常,但是m_image指向記憶體依然會洩漏
2 解決步驟
首先,對每個在heap中的記憶體資源進行記錄,會增加維護成本和開銷,導致程式效率低下,臃腫。比較好的解決辦法時,當問題出現時,集中處理,然後再進一步丟擲異常。
BookEntry::BookEntry(const string &name, const string& address, const string& image, const string& audio) { try { if (image != "") { m_image = new Image(image); } if (audio != "") { m_audio = new Audio(audio); } } catch (...) { // 解構函式和本動作相同,因此可包裝到一個函式中:cleanUp() cleanUp(); } } BookEntry::~BookEntry() { cleanUp(); } void BookEntry::cleanUp() { delete m_image; delete m_audio; }
3 初始化列表中的記憶體洩漏
上面對m_image和m_audio的初始化,放在了建構函式中。
但是,如果m_image變數宣告為const型別,就不得不將其初始化動作放到初始化列表中,我們就無法使用try…catch來捕捉異常,依然會造成m_image記憶體資源洩漏。如下面所示。
Image * const m_image; // 指標的指向不能修改
BookEntry::BookEntry(const string &name, const string& address,
const string& image, const string& audio)
: m_name(name), m_address(address),
m_image(image == "" ? 0 : new Image(image)),
m_audio(audio == "" ? 0 : new Audio(audio)) { // new Audio丟擲異常,會造成m_image記憶體洩漏
}
4 解決辦法
1 辦法一
一種辦法是,針對每個變數定義一個構建函式,在heap中建立變數的工作放到這個構建函式中,並返回建立好的指標。如下程式碼所示。
BookEntry::BookEntry(const string &name, const string& address,
const string& image, const string& audio)
: m_name(name), m_address(address),
m_image(createImage(image)),
m_audio(createAudio(audio)) {
}
Image* createImage(const std::string& image) {
Image *tmpImage = 0;
try {
// doSomething
} catch () {
}
return tmpImage;
}
2 辦法二
就像上一篇內容建議的,我們直接將指標包裹到auto_ptr中,利用“作用域”和“生命週期”來控制具體的行為。
std::auto_ptr<Image> m_image;
std::auto_ptr<Audio> m_audio;
當構造m_audio時,若出現任何異常,就像m_name, m_address一樣,m_image也會被自動銷燬,保證了記憶體不會被洩漏。