effective STL 當容器裡儲存指標時,記得要手動釋放

來源:互聯網
上載者:User

 

條款7:當使用new得指標的容器時,記得在銷毀容器前delete那些指標

STL中的容器非常優秀。它們提供了前向和逆向遍曆的迭代器(通過begin、end、rbegin等);它們能告訴你所容納的物件類型(通過value_type的typedef);在插入和刪除中,它們負責任何需要的記憶體管理;它們報告容納了多少對象和最多可能容納的數量(分別通過size和max_size);而且當然當容器自己被銷毀時會自動銷毀容納的每個對象。

 

給了這樣聰明的容器,很多程式員不再擔心用完以後的清除工作。呵呵,他們說,他們的容器會幫他們解決那個麻煩。在很多情況下,他們是對的,但當容器容納的是指向通過new分配的對象的指標時,他們就錯了。的確,當一個指標的容器被銷毀時,會銷毀它(那個容器)包含的每個元素,但指標的“解構函式”是無操作!它肯定不會調用delete。

結果,下面代碼直接導致一個記憶體流失:

void doSomething(){vector<Widget*> vwp;for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)vwp.push_back(new Widget);...// 使用vwp}// Widgets在這裡泄漏!

當vwp除了生存域後,vwp的每個元素都被銷毀,但那並不改變從沒有把delete作用於new得到的對象這個事實。那樣的刪除是你的職責,而不是vector的。這是一個特性。只有你知道一個指標是否應該被刪除。

通常,你需要它們被刪除。當情況如此時,可以很簡單地實現:

void doSomething() {vector<Widget*> vwp;... // 同上for (vector<Widget*>::iterator i = vwp.begin();i != vwp.end(),++i) { delete *i;} }

這可以工作,除非你不是對你“工作”的意思很吹毛求疵。一個問題是新的for迴圈代碼比for_each多得多,但沒有使用for_each來的清楚(參見條款43)。另一個問題是這段代碼不是異常安全的。如果在用指標填充了vwp和你要刪除它們之間拋出了一個異常,你會再次資源泄漏。幸運的是,兩個問題都可以克服。

要把你的類似for_each的迴圈轉化為真正使用for_each,你需要把delete轉入一個函數對象中。這像兒戲般簡單,假設你有一個喜歡和STL一起玩的孩子:

template<typename T>struct DeleteObject :// 條款40描述了為什麼public unary_function<const T*, void> {// 這裡有這個繼承void operator()(const T* ptr) const{delete ptr;}};

現在你可以這麼做:

void doSomething(){...// 同上for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);}

不幸的是,這讓你指定了DeleteObject將會刪除的對象的類型(在本例中是Widget)。那是很討厭的,vwp是一個vector<Widget*>,所以當然DeleteObject會刪除Widget*指標!咄!這種冗餘就不光是討厭了,因為它會導致很難跟蹤到的bug。假設,比如,有的人惡意地故意從string繼承:

class SpecialString: public string { ... }; 

這是很危險的行為,因為string,就像所有的標準STL容器,缺少虛解構函式,而從沒有虛解構函式的類公有繼承是一個大的C++禁忌。(詳細資料參考任意好的C++書。在《Effective C++》中,要看的地方是條款14。)但是,仍有一些人做這種事,所以讓我們考慮一下下面代碼會有什麼行為:

void doSomething(){deque<SpecialString*> dssp;...for_each(dssp.begin(), dssp.end(),// 行為未定義!通過沒有DeleteObject<string>());// 虛解構函式的基類}// 指標來刪除派生對象

注意dssp被聲明為容納SpecialString*指標,但for_each迴圈的作者告訴DeleteObject它將刪除string*指標。很容易知道會出現什麼樣的錯誤。SpecialString的行為當然很像string,所以如果它的使用者偶爾忘了他們用的是SpecialStrings而不是string是可以原諒的。

你可以通過編譯器推斷傳給DeleteObject::operator()的指標的類型來消除這個錯誤(也減少DeleteObject的使用者需要的擊鍵次數)。我們需要做的所有的事就是把模板化從DeleteObject移到它的operator():

struct DeleteObject {// 刪除這裡的// 模板化和基類template<typename T> // 模板化加在這裡void operator()(const T* ptr) const { delete ptr; } }

編譯器知道傳給DeleteObject::operator()的指標的類型,所以我們可以讓它通過指標的類型自動執行個體化一個operator()。這種類型演繹下降讓我們放棄使DeleteObject可適配的能力(參見條款40)。想想DeleteObject的設計目的,會很難想象那會是一個問題。

使用新版DeleteObject,用於SpecialString的客戶代碼看起來像這樣:

void doSomething(){deque<SpecialString*> dssp;...for_each(dssp.begin(), dssp.end(),DeleteObject());// 啊!良好定義的行為!}

直截了當而且型別安全,正如我們喜歡的一樣。

但仍不是異常安全的。如果在SpecialString被new但在調用for_each之前拋出一個異常,就會發生泄漏。那個問題可以以多種方式被解決,但最簡單的可能是用智能指標的容器來代替指標的容器,典型的是引用計數指標。(如果你不熟悉智能指標的概念,你應該可以在任何中級或進階C++書上找到描述。在《More Effective C++》中,這段材料在條款28。)

STL本身沒有包含引用計數指標,而且寫一個好的——一個總是可以正確工作的——非常需要技巧,除非必須否則你一定不想做。我在1996年的《More Effective C++》中發布了用於引用計數智能指標的代碼,儘管是基於已制定的智能指標實現並提交給由有經驗的開發人員做了很多發布前檢查,小bug報告還是持續了很多年。智能指標出錯的不同方式的數量很值得注意。)

幸運的是,基本上不需要你自己寫,因為經過檢驗的實現不難找到。一個這樣的智能指標是Boost庫中的shared_ptr。利用Boost的shared_ptr,本條款的原始例子可以重寫為這樣:

void doSomething(){typedef boost::shared_ ptr<Widget> SPW;//SPW = "shared_ptr// to Widget"vector<SPW> vwp;for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)vwp.push_back(SPW(new Widget));// 從一個Widget建立SPW,// 然後進行一次push_back...// 使用vwp}// 這裡沒有Widget泄漏,甚至// 在上面代碼中拋出異常

你不能有的愚蠢思想是你可以通過建立auto_ptr的容器來形成可以自動刪除的指標。那是很可怕的想法,非常危險。我在條款8討論了為什麼你應該避免它。

 

我們需要記住的所有事情就是STL容器很智能,但它們沒有智能到知道是否應該刪除它們所包含的指標。當你要刪除指標的容器時要避免資源泄漏,你必須用智能引用計數指標對象(比如Boost的shared_ptr)來代替指標,或者你必須在容器銷毀前手動刪除容器中的每個指標。

最後,你可能會想到既然一個類似DeleteObject的結構體可以簡化避免容納對象指標的容器資源泄漏,那麼是否可能建立一個類似的DeleteArray結構體來簡化避免容納指向數組的指標的容器資源泄漏呢?這當然可能,但是否明智則不是同一回事了。條款13解釋了為什麼動態分配數組幾乎總是劣於vector和string對象,所以在你要寫DeleteArray之前,請先看看條款13。運氣好的話,你會知道DeleteArray是永遠不會出現的結構體。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.