Effective STL 條款7

條款7.使用包含由new產生的指針容器時,切記在容器銷燬前delete指針

容器在STL中被認爲是智能的。它們支持向前和向後的迭代器;它們能告訴你它所保存的對象類型(通過typedef value_type);在插入和刪除過程中它們進行了良好的內存管理;它們將報告自己包含了多少對象和自己最多能包含多少對象(分別通過sizemax_size取得);並且,當容器銷燬時,它自動銷燬每個被包含的對象。

擁有如此聰明的容器,許多程序員自己不再擔心清理問題。他們認爲容器會爲他們操心。多數情況下,他們正確,但是當容器包括由new生產對象指針時,他們就不是太正確。毫無疑問,指針容器在銷燬時,會銷燬它所包容的每一個元素,但是指針的“析構函數”只是一個空操作。它不會調用delete

結果是,以下代碼直接導致內存資源泄漏:

void doSomething()<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

{

vector<Widget*> vwp;

for (int i = 0; i < SOME_MAGIC_NUMBER; ++i)

vwp.push_back(new Widget);

… // use vwp

} //Widgets are leaked here!

當離開vwp的作用範圍時,vwp的每一個元素都會被銷燬,但是這並不改變new所產生的對象沒有被delete這個事實。這個刪除動作是程序員的責任,而不是vector的。這其實是一個功能,因爲只有程序員才知道指針是否需要刪除。

通常,程序員希望它們那樣(刪除指針)。在那種情況(上例)中,使它發生其實很簡單。

void doSomething()

{

vector<Widget*> vwp;

… // as before

for (vector<Widget*>::iterator i = vwp.begin();i != vwp.end(),++i) {

delete *i;

}

這能行,如果你不是十分在意它只是“能行”。問題之一是新的for循環做了很多for_each做的事,但它不像for_each一樣清析。另一個問題是代碼不是異常安全。如果一個異常在vwp填上指針之後,而這些指針還沒有刪除之前被拋出。資源泄漏再次出現。幸運的是兩個問題都可以克服。

修改for_each類似的代碼以使用真正的for_each,需要將delete操作置於(仿)函數對象中。這像一個兒童遊戲,假設你有一個喜歡與STL一起玩遊戲的小孩。

template<typename T>

struct DeleteObject:                  // Item 40 describes why

public unary_function<const T*, void> { //this inheritance is here

 

void operator()(const T* ptr) const

delete ptr;

}

};

現在你可以這樣做:

void doSomething()

{

… // as before

for_each(vwp.begin(), vwp.end(), DeleteObject<Widget>);

}

不太走運,它要求指明DeleteObject刪除的對象類型(這裏是:Widget )。令人討厭,vwp是一個vector<Widget*>,因此DeteleObject刪除的當然是Widget指針!這種冗餘不只是令人討厭,因爲它可能導致難以檢查的bug出現。試想一下,例如,有人故意決定要從string繼承:

class SpecialString: public string { ...};

這是一種危險的產物,因爲string與其它標準STL容器一樣,沒有virtual析構函數。公有繼承一個沒有虛析構函數的對象是C++一個主要的誤區(a major C++ no-no(近一步的細節,在任何一個很好的C++書中都有討論。在Effective C++中,它被放在Item 14)。不論如何,有人如此作了。考慮一下以下代碼的行爲:

void doSomething()

{

deque<SpecialString*> dssp;

for_each( dssp.begin(), dssp.end(), // undefined behavior! Deletion

DeleteObject<string>()); //of a derived object via a base

} // class pointer where there is

//no virtual destructor

注意dssp聲明爲保存SpecialString的指針,但是for_each循環的作者告訴DeleteObject,它準備刪除string的指針。很容易發現什麼樣的錯誤會發生。SpecialString無疑在很大程度上表現爲string。因此有人會忘記它的用戶,他們會不記得他們使用的是SpecialString而不是string

可以排除這個錯誤(也可以減少DeleteObject用戶敲打鍵盤的次數)使用編譯器推繹出傳給DeleteObject::operator()的指針類型。所有工作只是把模板從DeleteObject類移到operator()上。

struct DeleteObject { // templatization and base

// class removed here

template<typename T> II templatization added here

void operator()(const T* ptr) const

{

delete ptr;

}

}

編譯器知道傳遞給DeleteObject::operator()的指針類型,因此它將自動爲該類型指針生成一個operator的實例。這種類型推繹的產生,取決於我們放棄DeleteObject的可適應性。考慮一下DeleteObject被設計爲如何使用,就很難找出可能發生問題的地方。

使用這一新版的DeleteObjectSpecialString客戶的代碼看起來像這樣:

void doSomething()

{

deque<SpecialString*> dssp;

for_each( dssp.begin(), dssp.end(),

DeleteObject ()); // ah! well-defined behavior!

}

直接而且類型安全,就像我們所喜歡的那樣。

但是它還不是異常安全。如果SpecialString生產了,但還沒有調用for_each,一個異常被拋出,泄漏將出現。這個問題可以用很多方法解決,但最簡單的也許是使用智能指針容器取代指針容器,通常使用一個引用記數的智能指針(如果不熟悉智能指針的概念,可以在中高級C++讀物中找到。在More Effective C++中,這些材料在Item 28。)

STL本身並不包括引用記數的智能指針,編寫一個好的-所有情況下都正確-太富有技巧,因此除非真的需要,並不需要這樣做。我(作者)1996年在More Effective C++發佈了了一個引用記數的智能指針,儘管它基於一些確定的智能指針實現,而且在發佈前由多位有經驗的開發者討論過,但是這些年還是有一堆準確的Bug被發現。很多引用記數的智能指針可能失敗的微妙情況被說明。(細節在More Effective C++勘誤中討論)

幸運地,幾乎不需要自己寫一個智能指針,因爲已驗正的實現並不難找。在Boost庫(參考條款50)中就有一個這樣的share_ptr。使用Boostshare_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);   // create a SPW from a

// Widget*, then do a

//push_back on it

                                 // use vwp

}   // no Widgets are leaked here, not

// even if an exception is thrown

//in the code above

千萬不能被auto_ptr愚弄了,不要認爲創建auto_ptr的容器,指針會被自動刪除。這是可怕是想法,它是如此危險,我準備用整個條款8來說明你不應使用它。

應記住的是STL容器是智能的,但它不足以知道是否要刪除它包含的指針。爲了避免資源泄漏,使用指針容器時應刪除指針。你需要使用智能指針或在容器銷燬前手工刪除每一個指針。

最後,一個類似於DeleteObject的結構可以方便地避免使用指針容器時的資源泄漏,這也許會使你聯想起,也許可能創建一個類似的DeleteArray,避免使用數組指針容器時的資源泄漏。當然,這是可能的,但是是否明智就是另一個問題了。條款13解釋了爲什麼動態申請數組總是不如vectorstring對象。所以在你坐下來寫DeleteArray之前,請先看一看條款13。如果幸運,DeleteArray的時代將永遠不會到來。

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