容器在STL中被認(rèn)為是智能的。它們支持向前和向后的迭代器;它們能告訴你它所保存的對(duì)象類型(通過typedef value_type);在插入和刪除過程中它們進(jìn)行了良好的內(nèi)存管理;它們將報(bào)告自己包含了多少對(duì)象和自己最多能包含多少對(duì)象(分別通過size和max_size取得);并且,當(dāng)容器銷毀時(shí),它自動(dòng)銷毀每個(gè)被包含的對(duì)象。

  擁有如此聰明的容器,許多程序員自己不再擔(dān)心清理問題。他們認(rèn)為容器會(huì)為他們操心。多數(shù)情況下,他們正確,但是當(dāng)容器包括由new生產(chǎn)對(duì)象指針時(shí),他們就不是太正確。毫無疑問,指針容器在銷毀時(shí),會(huì)銷毀它所包容的每一個(gè)元素,但是指針的“析構(gòu)函數(shù)”只是一個(gè)空操作。它不會(huì)調(diào)用delete。

  結(jié)果是,以下代碼直接導(dǎo)致內(nèi)存資源泄漏:

void doSomething()

{

vector<Widget*> vwp;

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

vwp.push_back(new Widget);

… // use vwp

} //Widgets are leaked here!

  當(dāng)離開vwp的作用范圍時(shí),vwp的每一個(gè)元素都會(huì)被銷毀,但是這并不改變new所產(chǎn)生的對(duì)象沒有被delete這個(gè)事實(shí)。這個(gè)刪除動(dòng)作是程序員的責(zé)任,而不是vector的。這其實(shí)是一個(gè)功能,因?yàn)橹挥谐绦騿T才知道指針是否需要?jiǎng)h除。

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

void doSomething()

{

vector<Widget*> vwp;

… // as before

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

delete *i;

}

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

  修改for_each類似的代碼以使用真正的for_each,需要將delete操作置于(仿)函數(shù)對(duì)象中。這像一個(gè)兒童游戲,假設(shè)你有一個(gè)喜歡與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;

}

};

  現(xiàn)在你可以這樣做:

void doSomething()

{

… // as before

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

}

  不太走運(yùn),它要求指明DeleteObject刪除的對(duì)象類型(這里是:Widget )。令人討厭,vwp是一個(gè)vector<Widget*>,因此DeteleObject刪除的當(dāng)然是Widget指針!這種冗余不只是令人討厭,因?yàn)樗赡軐?dǎo)致難以檢查的bug出現(xiàn)。試想一下,例如,有人故意決定要從string繼承:

class SpecialString: public string { ...};

  這是一種危險(xiǎn)的產(chǎn)物,因?yàn)閟tring與其它標(biāo)準(zhǔn)STL容器一樣,沒有virtual析構(gòu)函數(shù)。公有繼承一個(gè)沒有虛析構(gòu)函數(shù)的對(duì)象是C++一個(gè)主要的誤區(qū)(a major C++ no-no)(近一步的細(xì)節(jié),在任何一個(gè)很好的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循環(huán)的作者告訴DeleteObject,它準(zhǔn)備刪除string的指針。很容易發(fā)現(xiàn)什么樣的錯(cuò)誤會(huì)發(fā)生。SpecialString無疑在很大程度上表現(xiàn)為string。因此有人會(huì)忘記它的用戶,他們會(huì)不記得他們使用的是SpecialString而不是string。

  可以排除這個(gè)錯(cuò)誤(也可以減少DeleteObject用戶敲打鍵盤的次數(shù))使用編譯器推繹出傳給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()的指針類型,因此它將自動(dòng)為該類型指針生成一個(gè)operator的實(shí)例。這種類型推繹的產(chǎn)生,取決于我們放棄DeleteObject的可適應(yīng)性。考慮一下DeleteObject被設(shè)計(jì)為如何使用,就很難找出可能發(fā)生問題的地方。

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

void doSomething()

{

deque<SpecialString*> dssp;

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

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

}

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

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

  STL本身并不包括引用記數(shù)的智能指針,編寫一個(gè)好的-所有情況下都正確-太富有技巧,因此除非真的需要,并不需要這樣做。我(作者)1996年在More Effective C++發(fā)布了了一個(gè)引用記數(shù)的智能指針,盡管它基于一些確定的智能指針實(shí)現(xiàn),而且在發(fā)布前由多位有經(jīng)驗(yàn)的開發(fā)者討論過,但是這些年還是有一堆準(zhǔn)確的Bug被發(fā)現(xiàn)。很多引用記數(shù)的智能指針可能失敗的微妙情況被說明。(細(xì)節(jié)在More Effective C++勘誤中討論)

  幸運(yùn)地,幾乎不需要自己寫一個(gè)智能指針,因?yàn)橐羊?yàn)正的實(shí)現(xiàn)并不難找。在Boost庫(參考條款50)中就有一個(gè)這樣的share_ptr。使用Boost的share_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愚弄了,不要認(rèn)為創(chuàng)建auto_ptr的容器,指針會(huì)被自動(dòng)刪除。這是可怕是想法,它是如此危險(xiǎn),我準(zhǔn)備用整個(gè)條款8來說明你不應(yīng)使用它。

  應(yīng)記住的是STL容器是智能的,但它不足以知道是否要?jiǎng)h除它包含的指針。為了避免資源泄漏,使用指針容器時(shí)應(yīng)刪除指針。你需要使用智能指針或在容器銷毀前手工刪除每一個(gè)指針。

  最后,一個(gè)類似于DeleteObject的結(jié)構(gòu)可以方便地避免使用指針容器時(shí)的資源泄漏,這也許會(huì)使你聯(lián)想起,也許可能創(chuàng)建一個(gè)類似的DeleteArray,避免使用數(shù)組指針容器時(shí)的資源泄漏。當(dāng)然,這是可能的,但是是否明智就是另一個(gè)問題了。條款13解釋了為什么動(dòng)態(tài)申請數(shù)組總是不如vector和string對(duì)象。所以在你坐下來寫DeleteArray之前,請先看一看條款13。如果幸運(yùn),DeleteArray的時(shí)代將永遠(yuǎn)不會(huì)到來。