第1章 容器
第1條:慎重選擇容器類型。
標(biāo)準(zhǔn)STL序列容器:vector、string、deque和list。
標(biāo)準(zhǔn)STL關(guān)聯(lián)容器:set、multiset、map和multimap。
非標(biāo)準(zhǔn)序列容器slist和rope。slist是一個單向鏈表,rope本質(zhì)上是一“重型”string。
非標(biāo)準(zhǔn)的關(guān)聯(lián)容器hash_set、hase_multiset、hash_map和hash_multimap。
vector
vector作為標(biāo)準(zhǔn)關(guān)聯(lián)容器的替代。(見第23條)
幾種標(biāo)準(zhǔn)的非STL容器,包括數(shù)組、bitset、valarray、stack、queue和priority_queue。
你是否關(guān)心容器中的元素是如何排序的?如果不關(guān)心,選擇哈希容器.
容器中數(shù)據(jù)的布局是否需要和C兼容?如果需要兼容,就只能選擇vector。(見第16條)
元素的查找速度是否是關(guān)鍵的考慮因素?如果是,就要考慮哈希容器、排序的vector和標(biāo)準(zhǔn)關(guān)聯(lián)容器-或許這就是優(yōu)先順序。
對插入和刪除操作,你需要事務(wù)語義嗎?如果是,只能選擇list。因為在標(biāo)準(zhǔn)容器中,只有l(wèi)ist對多個元素的插入操作提供了事務(wù)語義。
deque是唯一的、迭代器可能會變?yōu)闊o效(插入操作僅在容器末尾發(fā)生時,deque的迭代器可能會變?yōu)闊o效)而指向數(shù)據(jù)的指針和引用依然有效的標(biāo)準(zhǔn)STL容器。
第2條:不要試圖編寫?yīng)毩⒂谌萜黝愋偷拇a。
如果你想編寫對大多數(shù)的容器都適用的代碼,你只能使用它們的功能的交集。不同的容器是不同的,它們有非常明顯的優(yōu)缺點。它們并不是被設(shè)計用來交換使用的。
你無法編寫?yīng)毩⒂谌萜鞯拇a,但是,它們(指客戶代碼)可能可以。
第3條:確保容器中的對象拷貝正確而高效。
copy in,copy out,是STL的工作方式,它總的設(shè)計思想是為了避免不必要的拷貝。使拷貝動作高效并且防止剝離問題發(fā)生的一個簡單辦法是使容器包含指針而不是對象。
第4條:調(diào)用empty而不是檢查size()是否為0。
理由很簡單:empty對所有的標(biāo)準(zhǔn)容器都是常數(shù)時間操作,而對一些list的實現(xiàn),size耗費線性時間。
第5條:區(qū)間成員函數(shù)優(yōu)先于與之對應(yīng)的單元素成員函數(shù)。
區(qū)間成員函數(shù)寫起來更容易,更能清楚地表達你的意圖,而且它們表現(xiàn)出了更高的效率。
第6條:當(dāng)心C++編譯器最煩人的分析機制。
把形參加括號是合法的,把整個形參的聲明(包括數(shù)據(jù)類型和形參名字)用括號括起來是非法的。
第7條:如果容器中包含了通過new操作創(chuàng)建的指針,切記在容器對象析構(gòu)前將指針delete掉。
STL很智能,但沒有智能到知道是否該刪除自己所包含的指針?biāo)赶虻膶ο蟮某潭?。為了避免資源泄漏,你必須在容器被析構(gòu)前手工刪除其中的每個指針,或使用引用計數(shù)形式的智能指針(比如Boost的sharedprt)代替指針。
第8條:切勿創(chuàng)建包含auto_ptr的容器對象。
拷貝一個auto_ptr意味著改變它的值。例如對一個包含auto_ptr的vector調(diào)用sort排序,結(jié)果是vector的幾個元素被置為NULL而相應(yīng)的元素被刪除了。
第9條:慎重選擇刪除元素的方法。
要刪除容器中指定值的所有對象:
如果容器是vector、string或deque,則使用erase-remove習(xí)慣用法。
SeqContainer
c.erase(remove(c.begin(),c.end(),1963),c.end());
如果容器是list,則使用list::remove。
如果容器是一個標(biāo)準(zhǔn)關(guān)聯(lián)容器,則使用它的erase成員函數(shù)。
要刪除容器中滿足特定條件的所有對象:
如果容器是vector、string或deque,則使用erase-remove_if習(xí)慣用法。
如果容器是list,則使用list::remove_if。
如果容器是一個標(biāo)準(zhǔn)關(guān)聯(lián)容器,則使用remove_copy_if和swap,或者寫一個循環(huán)遍歷容器的元素,記住當(dāng)把迭代器傳給erase時,要對它進行后綴遞增。
AssocCOntainer
...
AssocContainer
remove_copy_if(c.begin(), c.end(), inserter(goodValues, goodValues.end()),badValue);
c.swap(goodValues);
或
for(AssocContainer
if(badValue(*i)) c.erase(i++);
else ++i;
}
要在循環(huán)內(nèi)部做某些(除了刪除對象之外的)操作:
如果容器是一個標(biāo)準(zhǔn)序列容器,則寫一個循環(huán)來遍歷容器中的元素,記住每次掉用erase時,要用它的返回值更新迭代器。
如果容器是一個標(biāo)準(zhǔn)關(guān)聯(lián)容器,則寫一個循環(huán)來遍歷容器中的元素,記住每次把迭代器傳給erase時,要對迭代器做后綴遞增。
第10條:了解分配子(allocator)的約定和限制。
第11條:理解自定義分配子的合理用法。
第12條:切勿對STL容器的線程安全性有不切實際的依賴。
對一個STL實現(xiàn)你最多只能期望:
多個線程讀是安全的。
多個線程對不同的容器寫入操作是安全的。
你不能期望STL庫會把你從手工同步控制中解脫出來,而且你不能依賴于任何線程支持。
第2章vector和string
第13條:vector和string優(yōu)先于動態(tài)分配的數(shù)組。
如果用new,意味著你要確保后面進行了delete。
如果你所使用的string是以引用計數(shù)來實現(xiàn)的,而你又運行在多線程環(huán)境中,并認為string的引用計數(shù)實現(xiàn)會影響效率,那么你至少有三種可行的選擇,而且,沒有一種選擇是舍棄STL。首先,檢查你的庫實現(xiàn),看看是否可以禁用引用計數(shù),通常是通過改變某個預(yù)處理變量的值。其次,尋找或開發(fā)一個不使用引用計數(shù)的string實現(xiàn)。第三,考慮使用vector
第14條:使用reserve來避免不必要的重新分配。
通常有兩種方式來使用reserve以避免不必要的重新分配。第一種方式是,若能確切知道或大致預(yù)計容器中最終會有多少個元素,則此時可使用reserve。第二種方式是,先預(yù)留足夠大的空間,然后,當(dāng)把所有的數(shù)據(jù)都加入后,再去除多余的容量。
第15條:注意string實現(xiàn)的多樣性。
如果你想有效的使用STL,那么你需要知道string實現(xiàn)的多樣性,尤其是當(dāng)你編寫的代碼必須要在不同的STL平臺上運行而你又面臨著嚴格的性能要求的時候。
第16條:了解如何把vector和string數(shù)據(jù)傳給舊的API。
如果你有個vector v,而你需要得到一個只想v中的數(shù)據(jù)的指針,從而可把數(shù)據(jù)作為數(shù)組來對才,那么只需要使用&v[0]就可以了,也可以用&*v.begin(),但是不好理解。對于string s,隨應(yīng)的形式是s.c_str()。
如果想用來自C API的數(shù)據(jù)來初始化一個vector,那么你可以利用vector和數(shù)組的內(nèi)存布局兼容性,先把數(shù)據(jù)寫入到vector中,然后把數(shù)據(jù)拷貝到期望最終寫入的STL容器中。
第17條:使用“swap技巧”出去多余的容量。
vector
表達式vector
同樣的技巧對string也實用:
string s;
...
string(s).swap(s);
第18條:避免使用vector
作為STL容器,vector
第3章 關(guān)聯(lián)容器
第19條:理解相等(equality)和等價(equivalence)的區(qū)別。
標(biāo)準(zhǔn)關(guān)聯(lián)容器總是保持排列順序的,所以每個容器必須有一個比較函數(shù)(默認為less)。等價的定義正是通過該比較函數(shù)而確定的。相等一定等價,等價不一定相等。