Effective STL中文版:50條有效使用STL的經驗

Effective STL中文版:50條有效使用STL的經驗

世界級C++大師Scott Meyers之Effective三部曲之一,中國C++技術先驅及著名作譯者潘愛民先生經典譯作

編輯推薦

世界級C++大師Scott Meyers之Effective三部曲之一,中國C++技術先驅及著名作譯者潘愛民先生經典譯作——如同Meyers的其他著作一樣,本書充滿了從實踐中總結出來的智慧,其清晰、簡明、透徹的風格,必將使每一位STL程式設計師從中獲益。

內容提要

C++標準模板庫(STL)是革命性的,但是要想學會並用好卻並不容易。 Scott Meyers(Effective C++與More effective c++的作者)揭示了專家總結的一些關鍵規則,既有專家們總是採用的做法,也有專家們總是避免的做法。通過這些規則,STL程式設計師可以最大限度地使用STL。在講述50條指導原則時, 本書提供了透徹的分析和深刻的實例,以讓讀者學到要做什麼,什麼時候該這樣做,以及為什麼要這樣做。

目錄

引言...............................................................................................................1

1容器..........................................................................................9

第1 條:慎重選擇容器類型。...............................................................9

第2 條:不要試圖編寫獨立於容器類型的代碼。....................................12

第3 條:確保容器中的對象拷貝正確而高效。.......................................16

第4 條:調用empty 而不是檢查size()是否為0。................................... 18

第5 條:區間成員函式優先於與之對應的單元素成員函式。............................20

第6 條:當心C++編譯器最煩人的分析機制。.................................................... 26

第7 條:如果容器中包含了通過new 操作創建的指針,切記在容器對象析構前將

指針delete 掉。.........................................................................28

第8 條:切勿創建包含auto_ptr 的容器對象。......................................... 32

第9 條:慎重選擇刪除元素的方法。...............................................................34

第10 條:了解分配子(allocator)的約定和限制。........................................... 38

第11 條:理解自定義分配子的合理用法。.............................................44

第12 條:切勿對STL 容器的執行緒安全性有不切實際的依賴。......................... 47

2 vector 和string.....................................................................................51

第13 條:vector 和string 優先於動態分配的數組。.............................................51

第14 條:使用reserve 來避免不必要的重新分配。............................................. 53

第15 條:注意string 實現的多樣性。.......................................................... 55

第16 條:了解如何把vector 和string 數據傳給舊的API。.................................... 60

第17 條:使用“swap 技巧”除去多餘的容量。................................................... 63

第18 條:避免使用vector<bool>。.................................................................................64

3關聯容器..........................................................................................................................67

第19 條:理解相等(equality)和等價(equivalence)的區別。......................................67

第20 條:為包含指針的關聯容器指定比較類型。........................................71

第21 條:總是讓比較函式在等值情況下返回false。.............................................. 74

第22 條:切勿直接修改set 或multiset 中的鍵。...............................................77

第23 條:考慮用排序的vector 替代關聯容器。............................................... 82

第24 條:當效率至關重要時,請在map::operator[]與map::insert 之間謹慎做出選擇。..... ...................... 87

第25 條:熟悉非標準的散列容器。...........................................................................91

4疊代器..............................................................................................................95

第26 條:iterator 優先於const_iterator、reverse_iterator及const_reverse_iterator。.....95

第27 條:使用distance 和advance 將容器的const_iterator轉換成iterator。............ 98

第28 條:正確理解由reverse_iterator 的base()成員函式所產生的iterator 的用法。.... ...................... 101

第29 條:對於逐個字元的輸入請考慮使用istreambuf_iterator。......................... 103

5算法...............................................................................................................................106

第30 條:確保目標區間足夠大。.......................................................................107

第31 條:了解各種與排序有關的選擇。................................................................110

第32 條:如果確實需要刪除元素,則需要在remove 這一類算法之後調用erase。...........115

第33 條:對包含指針的容器使用remove 這一類算法時要特別小心。.....................118

第34 條:了解哪些算法要求使用排序的區間作為參數。..................................121

第35 條:通過mismatch 或lexicographical_compare 實現簡單的忽略大小寫的字元

串比較。..................................................................124

第36 條:理解copy_if 算法的正確實現。............................................................. 128

第37 條:使用accumulate 或者for_each 進行區間統計。....................................130

6函式子、函式子類、函式及其他...........................................................................135

第38 條:遵循按值傳遞的原則來設計函式子類。.....................................135

第39 條:確保判別式是“純函式”。.................................................................138

第40 條:若一個類是函式子,則應使它可配接。...............................................141

第41 條:理解ptr_fun、mem_fun 和mem_fun_ref 的來由。................................. 145

第42 條:確保less<T>與operator<具有相同的語義。........................................148

7在程式中使用STL..............................................................................................152

第43 條:算法調用優先於手寫的循環。.................................................................152

第44 條:容器的成員函式優先於同名的算法。........................................................159

第45 條:正確區分count、find、binary_search、lower_bound、upper_bound 和

equal_range。....... ......................161

第46 條:考慮使用函式對象而不是函式作為STL 算法的參數。.................................. 168

第47 條:避免產生“直寫型”(write-only)的代碼。................................................ 172

第48 條:總是包含(#include)正確的頭檔案。................................................. 175

第49 條:學會分析與STL 相關的編譯器診斷信息。..................................... 176

第50 條:熟悉與STL 相關的Web 站點。...........................................................183

參考書目........................................................................................189

附錄A 地域性與忽略大小寫的字元串比較.................................................193

附錄B 對Microsoft 的STL 平台的說明..........................................202

精彩節摘

引言

你已經熟悉STL 了。你知道怎樣創建容器、怎樣遍歷容器中的內容,知道怎樣添加和刪除元素,以及如何使用常見的算法,比如find 和sort。但是你並不滿意。你總是感到自己還不能充分地利用STL。本該很簡單的任務卻並不簡單;本該很直接的操作卻要么泄漏資源,要么結果不對;本該更有效的過程卻需要更多的時間或記憶體,超出了你的預期。是的,你已經知道如何使用STL 了,但是你並不能確定自己是否在有效地使用它。

所以我為你寫了這本書。

在本書中,我將講解如何綜合STL 的各個部分,以便充分利用該庫的設計。這些信息能夠讓你為簡單而直接的問題設計出簡單而直接的解決方案,也能幫助你為更複雜的問題設計出優雅的解決方案。我將指出一些常見的STL 用法錯誤,並指出該如何避免這樣的錯誤。這能幫助你避免產生資源泄漏、寫出不能移植的代碼,以及出現不確定的行為。我還將討論如何對你的代碼進行最佳化,從而可以讓STL 執行得更快、更流暢,就像你所期待的那樣。

本書中的信息將會使你成為一位更優秀的STL 程式設計師;它會使你成為一位高效率、高產出的程式設計師;它還會使你成為一位快樂的程式設計師。使用STL 很令人開心,但是有效地使用它則更令人開心,這種開心來源於它會使你有更多的時間離開鍵盤,因為你可能不相信自己會節省這么多時間。即便是對STL 粗粗瀏覽一遍,也能發現這是一個非常酷的庫,但你可能想像不到實際上它還要酷得多(無論是深度還是廣度)。本書的一個主要目標是向你展示這個庫是多么令人驚奇,因為我從事程式設計近三十年來,從來沒看到過可以與STL 相媲美的代碼庫。可能你也沒見過。

定義、使用和擴展STL

STL 並沒有一個官方的正式定義,不同的人使用這個詞的時候,它有不同的含義。在本書中,STL 表示C++標準庫中與疊代器一起工作的那部分,其中包括標準容器(包含string)、iostream庫的一部分、函式對象和各種算法。它排除了標準容器配接器(stack、queue 和priority_queue)以及容器bitset 和valarray,因為它們缺少對疊代器的支持。數組也不包括在其中。不錯,數組支持指針形式的疊代器,但數組是C++語言的一部分,而不是STL 庫的一部分。

從技術上講,我對STL 的定義不包括標準C++庫的擴展部分,尤其是散列容器、單向鍊表、rope,以及許多非標準的函式對象。即便如此,一個高效的STL程式設計師需要意識到這種擴展,所以在適當的時候我也會提及。實際上,第25 條是專門針對非標準的散列容器的一般性介紹。現在它們還不在STL 中,但是一些與之類似的東西肯定會進入到下一個版本的標準C++庫中,我們展望一下未來總是有價值的。

STL 之所以存在擴展,其中一個原因是,STL 的設計目的就是為了便於擴展。但在本書中,我將把焦點放在如何使用STL 上,而不是如何向其中添加新的部件。比如,你會發現,我將很少講述如何編寫自己的算法,對於如何編寫新的容器和疊代器也沒有給出任何建議。我相信,在考慮增強STL 的能力之前,首先重要的是掌握STL 已經提供了什麼,而這正是本書的焦點所在。當你決定創建自己的類似STL 的部件時,可以在Josuttis 的The C++ Standard Library[3]和Austern 的GenericProgramming and the STL[4]中找到相關的建議,它們會告訴你如何做到這一點。然而,在本書中,我還是會討論到STL 擴展的一個方面,即怎樣編寫自己的函式對象。如果不知道怎樣編寫自己的函式對象,你就無法有效地使用STL,所以我將花一整章的篇幅(第6 章)來重點講述這一話題。

引文

上面的段落中對於Josuttis 和Austern 的著作的引用方式,正是我在本書中對於參考書籍的引用方式。一般情況下,對於被引用到的工作,我儘可能地提及足夠多的信息,以便讓那些對此熟悉的人能夠確定這一點。比如,如果你已經熟悉這些作者的著作,那么你就不必翻到後面的參考書目表去查找[3]和[4]來找到這些你已經知道的書籍。當然,如果你對某一個出版物還不太熟悉,則本書正文後所附的參考書目表會給出完整的引用。

本書中,我對於三項工作的引用特別頻繁,以至於我通常把引用的序號都省略了。第一項是C++國際標準[5],提到的時候我往往會簡單地稱作“C++標準”。其他兩項是我以前寫的兩本C++方面的書:Effective C++[1]和More EffectiveC++[2]。

STL 和標準

我會經常提到C++標準,因為本書的重點在於講述可移植的、與標準兼容的C++。理論上講,在本書中我所給出的內容對於任何一個C++實現都適用。可實際上卻並不是這樣,編譯器和STL 實現這兩方面的不足使得有些本該有效的代碼無法編譯,或者編譯之後的代碼無法如預期般地執行。對於較為普遍的此類情形,我會指出問題所在,並解釋如何能夠繞過它。

有時候,最直接的方式是使用不同的STL 實現。附錄B 給出了一個這樣的例子。你對STL的使用越多,就越有必要區分你的編譯器和你的庫實現。當程式設計師試圖使合法的代碼通過編譯,卻未能如願時,他們通常會埋怨編譯器,但是對於STL,這可能並不是編譯器的問題,而是STL的實現出了問題。為了進一步強調“你要同時依賴於編譯器和庫的實現”這一事實,我使用了術語STL 平台。STL 平台是指一個特定的編譯器和一個特定的STL 實現的組合。在本書中,如果我提到了一個編譯器問題,那么你可以確信,我的確認為編譯器是罪魁禍首。但是,如果我提到的是你的STL 平台的問題,那么你可以理解為“可能是編譯器的錯誤,也可能是庫的錯誤,或者二者都有錯誤”。

我通常用複數形式來稱呼你的“編譯器”(compilers),因為長期以來我一直認為如果你能保證你的代碼對於多個編譯器都能工作,那么你就提高了代碼的質量(尤其是可移植性)。而且,使用多個編譯器通常會使你更易於理解由於不適當地使用STL 而引起的晦澀的錯誤信息(第49條專門講述如何解讀這些信息)。

我之所以強調與標準兼容的代碼,其中一個原因是,你可以避免使用那些導致不確定行為的語言成分。在運行時,這些成分可能會做出任何事情來。不幸的是,這意味著它們可能恰好做了你所需要的工作,從而導致一種錯誤的安全感。太多的程式設計師認為不確定的行為肯定會導致明顯的問題,比如記憶體頁面保護錯誤或者其他災難性的運行失敗。實際上,不確定行為的結果可能要微妙得多,比如導致破壞很少被引用的記憶體。多次運行程式可能會有不同的表現。我認為對於“不確定行為”,一個可行的定義是“對我可以正常工作,對你可以正常工作,在開發和QA 中都可以工作,但是在你最重要的顧客面前,卻失敗了。”避免不確定行為很重要,所以我將指出可能發生這種行為的一些常見情形。你應該訓練自己,以便對於這樣的情形保持高度警惕。

引用計數

如果不提到引用計數技術而來討論STL,這幾乎是不可能的。在第7 條和第33 條中你將會看到,凡是涉及指針容器的設計幾乎無一例外地會用到引用計數。另外,很多string 實現的內部也使用了引用計數技術,正如在第15 條中指出的那樣,這是你無法忽略的一個實現細節。在本書中,我將假設你熟悉有關引用計數的一些基本知識。如果你不熟悉,大多數中級和高級的C++書籍都涉及了這一話題。比如,在MoreEffective C++中,相關的材料在第28 條和第29 條中。如果你不知道引用計數是什麼,而且你也不想知道,那么,請不要著急,你仍然可以讀懂本書,儘管這裡或那裡有一些句子你可能不太懂。

string和wstring

我所說的關於string 的內容同樣也適用於與它對應的寬位元組字元串wstring。同樣,當提到string 與char 或char*的關係時,同樣的關係也適用於wstring 與wchar_t 或wchar_t*。換句話說,不要因為我沒有顯式地提到寬位元組字元串,就認為STL 對此不提供支持。STL 既支持基於char 的字元串,也支持寬位元組字元串。string 和wstring 是同一個模板(即basic_string)的實例。

術語,術語,術語

這不是一本關於STL 的入門書,所以我假定你已經知道了基本的概念。但是,下面的術語很重要,我認為有必要回顧一下:

„ vector、string、deque 和list 被稱為標準序列容器。標準關聯容器是set、multiset、map 和multimap。

„根據疊代器所支持的操作,可以把疊代器分為5 類。簡單來說,輸入疊代器(input iterator)是唯讀疊代器,在每個被遍歷到的位置上只能被讀取一次。輸出疊代器(output iterator)是只寫疊代器,在每個被遍歷到的位置上只能被寫入一次。輸入和輸出疊代器的模型分別是建立在針對輸入和輸出流(例如檔案)的讀/寫操作的基礎上的。所以不難理解,輸入和輸出疊代器最常見的表現形式是istream_iterator 和ostream_iterator。

前向疊代器(forward iterator)兼具輸入和輸出疊代器的能力,但是它可以對同一個位置重複進行讀和寫。前向疊代器不支持operator--,所以它只能向前移動。所有的標準STL容器都支持比前向疊代器功能更強大的疊代器,但是,你在第25 條中可以看到,散列容器的一種設計會產生前向疊代器。單向鍊表容器(見第50 條)也提供了前向疊代器。

雙向疊代器(bidirectional iterator)很像前向疊代器,只是它們向後移動和向前移動同樣容易。標準關聯容器都提供了雙向疊代器。list 也是如此。

隨機訪問疊代器(random access iterator)有雙向疊代器的所有功能,而且,它還提供了“疊代器算術”,即向前或向後跳躍一步的能力。vector、string 和deque 都提供了隨機訪問疊代器。指向數組內部的指針對於數組來說也是隨機訪問疊代器。

„所有重載了函式調用操作符(即operator())的類都是一個函式子類(functorclass)。從這些類創建的對象被稱為函式對象(functionobject)或函式子(functor)。在STL中,大多數使用函式對象的地方同樣也可以使用實際的函式,所以我經常使用“函式對象”(function object)這個術語既表示C++函式,也表示真正的函式對象。

„函式bind1st 和bind2nd 被稱為綁定器(binder)。

STL 一個革命性的方面是它的計算複雜性保證。這些保證限制了一個STL 操作可以做多少工作。這樣做很棒,因為它能幫助你確定用於解決同一問題的不同方法之間的相對效率,而跟你所使用的STL 平台無關。不幸的是,如果你沒有正式學過計算機科學的專用名詞,則計算複雜性保證背後的術語可能會使你迷惑。下面是本書中所用到的複雜性術語的一個簡要介紹。這裡的每一種情況都提到了:用n 的函式來表示做一件事情所需要的時間,其中n 是容器中或區間中元素的個數。

„常數時間(constant time)內完成的操作,其性能不受n 變化的影響。比如,把一個元素插入到list 中是一個常數時間操作。不管鍊表中有一個還是一百萬個元素,插入所花費的時間是一樣的。

不要只從字面上理解“常數時間”。這並不意味著做某種操作花費的時間是一個字面上的常數,它只意味著所需時間不受n 的影響。比如,對同樣的“常數時間”操作,兩個STL平台所需的時間可能相差很大。當一個庫的實現比另一個實現更複雜或者一個編譯器比另一個做了更高強度的最佳化時,就可能會發生這種情況。

常數時間複雜性的一個變種是分攤的常數時間(amortized constant time)。分攤的常數時間內運行的操作通常是常數時間的操作,但偶爾它們也花費與n 相關的時間。分攤的常數時間操作往往在常數時間內運行。

„對數時間(logarithmic time)內完成的操作,當n 變大時需要更多的時間,但它需要的時間與n 的對數成正比增長。比如,對一百萬個元素的操作所需的時間僅僅是對一百個元素操作的三倍,因為log n3 = 3 log n。對關聯容器的大多數查找算法(如set::find)是對數時間操作。

„線性時間(linear time)內完成的操作,所需的時間與n 成正比增長。標準算法count需要線性時間,因為對所給區間中的每個元素它都要做檢查。如果區間大小變為原來的三倍,則它需要做三倍的工作,所需的時間也是原來的三倍。

一般來說,常數時間操作比對數時間操作更快,而對數時間操作又快於線性性能的操作。當n 足夠大時,總會是這樣的;但是對於相對較小的n,有時理論上更複雜的操作反而會比理論上更簡單的操作性能更好。如果你想知道更多關於STL 複雜性的信息,可以參閱Josuttis 的The C++ Standard Library。

關於術語最後還要提一點,別忘了map 或multimap 中的每個元素都有兩部分。我通常把第一部分稱作鍵(key),而把第二部分稱作值(value)。以

map<string, double> m;

為例,string 是鍵而double 是值。

譯序

就像本書的前兩本姊妹作(Effective C++、More EffectiveC++)一樣,本書的側重點仍然在於提升讀者的經驗,只不過這次將焦點瞄準了C++標準庫,而且是其中最有意思的一部分——STL。

C++是一門易學難用的程式語言,從學會使用C++到用好C++需要經過不斷的實踐。Scott Meyers 的這三本姊妹作分別從各個不同的角度來幫助你縮短這個過程。C++語言經過了近二十年的發展,已漸趨完善。儘管如此,在使用C++語言的時候,仍然有許多陷阱,有的陷阱非常明顯,一經點撥就可以明白;而有的陷阱則不那么直截了當,需要仔細地分析才能揭開那層神秘的面紗。

本書是針對STL 的經驗總結,書中列出了50 個條款,絕大多數條款都解釋了在使用STL時應該注意的某一個方面的問題,並且詳盡地分析了問題的來源、解決方案的優劣。這是作者

在教學和實踐過程中總結出來的經驗,其中的內容值得我們學習和思考。

STL 的源碼規模並不大,但是它蘊含的思想非常深刻。在C++標準化的過程中,STL 也被定格和統一。對於每一個STL 實現,我們所看到的被分為兩部分:一是STL 的接口,這是應用程式賴以打交道的基礎,也是我們所熟知的STL;二是STL 的實現,特別是一些內部的機理,有的機理是C++標準所規定的,但是有的卻是實現者自主選擇的。在軟體設計領域中有一條普遍適用的規則是“接口與實現分離”,但是對於STL,你不能簡單地使用這條規則。雖然你寫出來的程式代碼只跟STL 的接口打交道,但是用好STL 則需要建立在充分了解STL 實現的基礎之上。你不僅需要了解對所有STL 實現都通用的知識,也要了解針對你所使用的STL 實現的特殊知識。那么,你該如何來把握接口與實現之間的關係呢?本書講述了許多既涉及接口也關係到具體實現的STL 用法,通過對這些用法的講解,讀者可以更加清楚地了解應該如何來看待這些與實現相關的知識。

這兩年來,有關STL 的書籍越來越多,而且許多C++書籍也開始更加關注於STL 這一部分內容。對於讀者來說,這無疑是一件好事,因為STL 難學的問題終於解決了。我們可以看到,像vector和string 等常用的STL 組件幾乎出現在任何一個C++程式中。但是,隨之而來的STL難用的問題卻暴露出來了,程式設計師要想真正發揮STL的強大優勢並不容易。在現有的STL 書籍中,像本書這樣指導讀者用好STL 的書籍並不多見。

本書沿襲了作者一貫的寫作風格,以條款的形式將各種使用STL 的經驗組織在一起,書中主要包括以下內容:

如何選擇容器的類型。STL 中容器的類型並不多,但是不同的容器有不同的特點,所以選擇恰當的容器類型往往是解決問題的起點。本書中還特別介紹了與vector 和string兩種容器有關的一些注意事項。

涉及關聯容器有更多的陷阱,一不留神就可能陷入其中。作者專門指出了關聯容器中一些並不直觀的要點,還介紹了一種非標準的關聯容器——散列容器。

疊代器是STL 中指針的泛化形式,也是程式設計師訪問容器的重要途徑。本書討論了與const_iterator 和reverse_iterator有關的一些問題。以我個人之見,本書這部分內容略顯單薄,畢竟疊代器在STL 中是一個非常關鍵的組件。

STL 算法是體現STL 功能的地方,一個簡單的算法調用或許完成了一件極為複雜的事情,但是要用好STL 中眾多的算法並不容易,本書給出了一些重要的啟示。

函式對象是STL 中用到的關鍵武器之一,它使得STL 中每一個算法都具有極強的擴展性,本書也特別討論了涉及函式和函式對象的一些要點。

其他方方面面,包括在算法和同名成員函式之間如何進行區別、如何考慮程式的效率、如何保持程式的可讀性、如何解讀調試信息、關於移植性問題的考慮,等等。

本書並沒有面面俱到地介紹所有要注意的事項,而只是挑選了一些有代表性的,也是最有普遍適用性的問題和例子作為講解的內容。有些問題並沒有完美的解決方案,但是,作者已經把這個問題為你分析透了,所以最終的解決途徑還要取決於作為實踐者的你。

本書的翻譯工作是我和陳銘、鄒開紅合作完成的,其中鄒開紅完成了前25 條的初譯工作,陳銘完成了後25 條的初譯工作,最後我完成了所有內容的終稿工作,同時我也按照原作者給出的勘誤作了修訂。錯誤之處在所難免,請讀者諒解。

對於每一個期望將STL 用得更好的人,這本書值得一讀。

潘愛民

2003 年6 月16 日於北京大學燕北園

作者簡介

Scott Meyers

世界頂級C++軟體開發技術權威之一。他是兩本暢銷書Effective C++和More EffectiveC++的作者,以前曾經是C++ Report的專欄作家。他經常為C/C++ Users Journal和Dr. Dobb'sJournal撰稿,也為全球範圍內的客戶做諮詢活動。他也是AdvisoryBoards for NumeriX LLC和InfoCruiser公司的成員。他擁有Brown University的計算機科學博士學位。

潘愛民

任職於阿里雲計算有限公司,擔任阿里雲OS首席架構師。長期從事軟體和系統技術的研究與開發工作,撰寫了大量軟體技術文章,著譯了多部經典計算機圖書,在國內外學術刊物上發表了30多篇文章。曾經任教於北京大學和清華大學(兼職)。後進入工業界,先後任職於微軟亞洲研究院、盛大網路發展有限公司和阿里雲計算有限公司。目前也是工信部移動作業系統專家組成員。

潘愛民獲得了數學學士學位和計算機科學博士學位,主要研究領域包括軟體設計、信息安全、作業系統和網際網路技術。

媒體評論

本書的亮點包括以下幾個方面:

關於選擇容器的建議,其中涉及到的容器有:標準STL容器(例如vector和list)、非標準的STL容器(例如hash_set和hash_map),以及非STL容器(例如bitset)。

一些改進效率的技術,通過它們可以最大程度地提高STL(以及使用STL的程式)的效率。

一些深層次的知識,其中涉及到疊代器、函式對象和分配子(allocator)的行為,也包括程式設計師總是應該避免的做法。

對於那些同名的算法和成員函式,如find,根據它們行為方式上的微妙差異,本書給出了一些指導原則,以保證它們能被正確地使用。

本書也討論了潛在的移植性問題,包括如何避免這些移植性問題的各種簡單途徑。

前言

Itcame without ribbons! It came without tags!

Itcame without packages, boxes or bags!

—— Dr. Seuss, How the Grinch Stole

Christmas!,Random House, 1957

我第一次寫關於STL(Standard TemplateLibrary,標準模板庫)的介紹是在1995 年,當時我在More Effective C++的最後一個條款中對STL 做了粗略的介紹。此後不久,我就陸續收到一些電子郵件,詢問我什麼時候開始寫Effective STL。

有好幾年時間我一直在拒絕這種念頭。剛開始的時候,我對STL 並不非常熟悉,根本不足以提供任何關於STL 的建議。但是隨著時間的推移,以及我的經驗的增長,我的想法開始有了變化。毫無疑問,STL 庫代表了程式效率和擴展性設計方面的一個突破,但是當我開始真正使用STL 的時候,卻發現了許多我原來不可能注意到的實際問題。除了最簡單的STL 程式以外,要想移植一個稍微複雜一點的STL 程式都會面臨各種各樣的問題,這不僅僅是因為STL 庫實現有各自的特殊之處,而且也是因為底層的編譯器對於模板的支持各不相同——有的支持非常好,但有的卻非常差。要獲得STL 的正確指南並不容易,所以,學習“STL 的編程方式”非常困難,即使在克服了這個階段的障礙之後,你要想找到一份既容易理解又描述精確的參考文檔仍然是一大困難。可能最沮喪的是,即使一個小小的STL 用法錯誤,也常常會導致一大堆的編譯器診斷信息,而且每一條診斷信息都可能有上千個字元長,並且大多數會引用到一些在原始碼中根本沒有提到的類、函式或者模板(幾乎都很難理解)。儘管我對STL 讚賞有加,並且對STL 背後的人們更是欽佩無比,但是要向從事實際開發工作的程式設計師推薦STL 卻感到非常不舒服。因為,我自己並不確定要有效地使用STL 是否是可能的。

然後,我開始注意到了一些讓我非常驚訝的事情。儘管STL 存在可移植性問題,儘管它的文檔並不完整,儘管編譯器的診斷信息有如傳輸線上的噪聲一樣,但是,我的許多諮詢客戶正在使用STL。而且,他們並不只是把STL 拿來玩一玩,而是在用它開發實際的產品。這是一個很重要的啟示。過去我知道STL 是一個設計非常考究的模板庫,這時我逐漸感覺到,既然程式設計師們願意忍受移植性的麻煩、不夠完整的文檔及難以理解的錯誤訊息,那么這個庫除了良好的設計以外,一定還有其他更多的優勢。隨著專業程式設計師的數量越來越多,我意識到,即使是一個很差的STL 實現,也勝過沒有實現。

更進一步,我知道STL 的境況正在好轉。C++庫和編譯器越來越多地遵從C++標準,好的文檔也開始出現了(請參考本書後面所附的參考資料目錄),而且編譯器的診斷信息也在改進(不過我們還需要等待它們改進得更好,在此期間,你可以參考第49 條給出的一些針對如何處理診斷信息的建議)。因此我決定投身到這場STL 運動中,盡我的一份微薄之力。本書就是我努力的結果:50 條有效使用STL 的經驗。

我原來的計畫是在1999 年的下半年寫作本書,腦子裡一直是這樣想的,並且也有了一個提綱。但後來我改變了路線。我擱下了本書的寫作,而去開發一門有關STL 的引導性培訓課程,並且也教授了幾組程式設計師。大約一年以後,我又回到這本書的寫作上,並根據培訓課程中積累的經驗重新修訂了本書的提綱。就如同Effective C++成功地以實際程式設計師所面臨的問題為基礎一樣,我希望本書也以類似的方式來面對STL 編程過程中的各種實際問題,特別是那些對於專業開發人員尤為重要的實際問題。

致謝

我差不多用了兩年時間才真正對STL 有所認識,同時設計了一門關於STL 的培訓課程並著寫了本書,在此過程中,我得到了來自許多途徑的幫助。在所有的幫助途徑之中,有兩個尤其重要。第一是Mark Rodgers,當我設計培訓材料的時候,Mark總是自願審查這些材料,而且,我從他身上學到的關於STL 的知識,比從其他任何人身上學到的要多得多。他還擔當了本書的技術審稿人,給出了許多富有洞察力的意見和建議,幾乎每一個條款都得益於他的這些意見和建議。

另一個重要的信息來源是幾個與C++有關的Usenet 新聞組,尤其是comp.lang.c++.moderated(“clcm”)、comp.std.c++和microsoft.public.vc.stl。有十多年時間,我總是依靠參與像這樣的新聞組,來解答我自己的問題,審視我的各種思考。很難想像,如果沒有這些新聞組,我會怎么做。無論是為了這本書,還是我過去的C++出版物,我都要深深地感謝Usenet 社群所提供的幫助。

我對於STL 的理解受到了眾多出版物的影響,其中最重要的已列在本書後面的參考書目表中。尤其讓我受益良多的是Josuttis 的The C++Standard Library [3]。

本書基本上是其他許多人的見解和經驗的一份總結,儘管其中也有我自己的一些想法。我曾經試圖記述下我是在哪裡學到的哪些內容,但是這項任務做起來是毫無希望的,因為每一個條款都包含了很長一段時間中從各種途徑獲得的信息。下面的敘述是不完整的,但我已經竭盡所能了。請注意,我這裡的目標是,總結一下我是在哪裡首先得到了一個想法或者學到了一項技術,而並非該想法或技術最初是從哪兒發展起來的,或者由誰提出來的。

在第1 條中,我的見解“基於節點的容器為事務語義提供了更好的支持”建立在Josuttis的The C++Standard Library [3]的5.11.2 節的基礎之上。第2 條包含的一個例子來自於Mark Rodgers 的關於typedef 如何在分配子改變的情況下能有所幫助的論述。第5 條得到了Reeves 在C++ Report 上的專欄“STL Gotchas”[17]的啟發。第8 條來源於Sutter 的Exceptional C++中的第37 條,以及Kevin Henney 提供的關於“auto_ptr 的容器在實踐中如何未能工作”的重要細節。Matt Austern 在Usenet 的帖子中,提供了一些關於分配子何時有用的例子,我把他的例子包含在第11 條中。第12 條建立在SGI STL Web 站點[21]上關於執行緒安全性的討論基礎之上。第13條中關於在多執行緒環境下引用計數技術性能問題的材料來自於Sutter 在這個話題上的文章。第15 條的想法來自於Reeves 在C++ Report 上的專欄“Using Standard string in the Real World, Part 2”。在第16 條中,Mark Rodgers 提出了我所展示的技術,即讓一個C API 將數據直接寫到一個vector 中。第17 條包含了Siemel Naran 和Carl Barron 在Usenet 上張貼的信息。我偷了Sutter 在C++ Report 上的專欄“When Is a Container Not a Container?”作為第18 條。在第20 條中,Mark Rodgers 貢獻了“通過一個解引用函式子把一個指針轉換成一個對象”的想法,Scott Lewandowski 提出了我所展示的DereferenceLess的版本。第21 條起源於Doug Harrison 張貼在microsoft.public.vc.stl上的內容,但是,將該條款的焦點限定在等值上的決定則是我自己做出的。第22 條則建立在Sutter 在C++ Report 上的專欄“Standard Library News: sets and maps”的基礎之上;Matt Austern 幫助我理解了標準化委員會的LibraryIssue #103 的狀況。第23條得到了Austern 在C++ Report 上的文章“Why you Shouldn’t User set – and What to Use Instead”的啟發;David Smallberg 為我的DataCompare 實現做了更為精細的加工。我介紹的Dinkumware 散列容器建立在Plauger 在C/C++ Users Journal 上的專欄“Hash Tables”的基礎之上。Mark Rodgers 並不贊成第26 條的全部建議,但是該條款原先的一個動機是,他觀察到有些容器的成員函式只接受iterator 類型的實參。我選擇第29 條是因為Usenet 上Matt Austern和James Kanze 所參與的一些討論,同時我也受到了Kreft和Langer 發表在C++ Report 上的文章“ASophisticated Implementation of User-Defined Inserters and Extractors”的影響。第30 條是由於Josuttis 在The C++ Standard Library的5.4.2 節中的討論。在第31 條中,Marco Dalla Gasperina 貢獻了利用nth_element來計算中間值的示例用法,通過該算法來找到百分比的用法則直接來自於Stroustrup 的The C++ Programming Language 的18.7.1 節。第32 條受到了Josuttis 在The C++Standard Library 的5.6.1 節中的材料的影響。第35 條起源於Austern 在C++ Report 上的專欄“How to DoCase-Insensitive String Comparison”,而且,James Kanze以及John Potter 的clcm 帖子幫助我加深了對於所涉及的各個問題的理解。我在第36 條中所展示的copy_if 實現,來自於Stroustrup 的The C++Programming Language。第39 條在很大程度上得到了Josuttis 的多份出版物的啟發,他在TheC++ Standard Library[3]、在StandardLibrary Issue #92,以及在其C++ Report 的文章“Predicates vs. Function Objects”[14]中講述了關於“statefulpredicates”的內容。在我的介紹中,我使用了他的例子,並且推薦了他提出的一種方案,不過,我使用了自己的術語“純函式”。Matt Austern 證實了我在第41 條中關於術語mem_fun和mem_fun_ref 的歷史的猜測。第42 條可以追溯到當我考慮是否可以違反該指導原則時,我從Mark Rodgers 處得到的一份講稿。MarkRodgers 也貢獻了第44 條中的見解:在map 和multimap上的非成員搜尋操作會檢查每個元素的兩個組件,而成員搜尋操作只檢查每個元素的第一個組件(鍵)。第45 條包含了眾多clcm 發貼者貢獻的信息,其中包括John Potter、MarcinKasperski、Pete Becker、Dennis Yelle 和David Abrahams。David Smallberg 提醒我,在執行基於等價性的搜尋,以及在排序的序列容器上進行計數時,要注意equal_range 的用法。AndreiAlexandrescu 幫助我更好地理解了第50 條中講述的“指向引用的引用”問題所發生的條件;針對此問題,我在Mark Rodgers 所提供的例子(在BoostWeb 站點)的基礎上,也模仿了一個類似的例子。

顯然,附錄A 中的材料應該歸功於Matt Austern。我感謝他不僅允許我將這些材料包含到本書中,而且他親自對這些材料做了調整,使其更適合於本書。

好的技術書籍要求在出版前經過全面的檢查,我有幸得益於一群天才的技術審稿人所提供的大量精闢的建議。Brian Kernighan 和Cliff Green 在很早時候根據本書的部分草稿提出了他們的建議,而下述人員仔細檢查了本書的完整原稿:Doug Harrison、Brian Kernighan、Tim Johnson、FrancisGlassborow、Andrei Alexandrescu、David Smallberg、Aaron Campbell、Jared Manning、Herb Sutter、Stephen Dewhurst、Matt Austern、Gillmer Derge、Aaron Moore、Thomas Becker、Victor Von,當然還有Mark Rodgers。Katrina Avery 為本書做了內容審查。

在準備一本書時,最為複雜的一項工作是尋找到好的技術審稿人。我要感謝John Potter 為我引薦了JaredManning 和Aaron Campbell。

Herb Sutter 很痛快地答應了幫助我在Microsoft Visual Studio .NET 的beta 版基礎上編譯和運行一些STL 測試程式,並且將程式的行為記錄下來,而Leor Zolman 則承擔了測試本書中所有代碼的艱巨任務。當然,任何遺留下來的錯誤都是我的過錯,不是Herb 或者Leor 的責任。

Angelika Langer 使我看清了STL 函式對象某些方面的中間狀態。本書並沒有很多地介紹函式對象,也許我應該多講述一些這方面的內容,但是,凡是本書中講到的內容極可能是正確的,至少我希望如此。

本書的印刷比以前的印刷要好得多,因為有一些目光敏銳的讀者將問題指出來,所以我有機會解決這些問題,他們是:Jon Webb、Michael Hawkins、Derek Price、Jim Scheller、Carl Manaster、Herb Sutter、Albert Franklin、George King、Dave Miller、Harold Howe、John Fuller、Tim McCarthy、John Hershberger、IgorMikolic-Torreira、Stephan Bergmann、Robert Allan Schwartz、John Potter、David Grigsby、Sanjay Pattni、Jesper Andersen、Jing Tao Wang、André Blavier、Dan Schmidt、Bradley White、Adam Petersen、Wayne Goertel、Gabriel Netterdag、Jason Kenny、Scott Blachowicz、Seyed H. Haeri、Gareth McCaughan、Giulio Agostini、Fraser Ross、WolframBurkhardt、Keith Stanley、Leor Zolman、Chan Ki Lok、Motti Abramsky、Kevlin Henney、Stefan Kuhlins、Phillip Ngan、Jim Phillips、Ruediger Dreier、Guru Chandar、CharlesBrockman、Day Barr、Eric Niebler、Sharad Kala、Declan Moran\Nick de Smith、David Callaway、Shlomi Frank、Andrea Griffini、Hans Eckardt、David Smallberg、Matt Page、Andy Fyfe、VincentStojanov、Randy Parker、Thomas Schell、Cameron MacMinn、Mark Davis、Giora Unger、Julie Nahil、Martin Rottinger、Neil Henderson、Andrew Savige和Molly Sharp。我要感謝他們,正是他們的幫助改進了Effective STL 的印刷。

我在Addison-Wesley的合作者包括John Wait(我的編輯,現在也是一位資深的副總裁)、Alicia Carey 和Susannah Buzard(他的助手n 和n+1)、John Fuller(產品協調人)、LarinHansen(封面設計人)、Jason Jones(全才的技術高手,尤其是在Adobe 開發的恐怖排版軟體方面)、MartyRabinowitz(他們的老闆,但是他自己也工作),以及CurtJohnson、Chanda Leary-Coutu和Robin Bruce(都是市場人才,但仍然十分友善)。

是Abbi Staley 讓我覺得周日的午餐是一種美好的享受。

我的妻子Nancy 一直以來對我的研究和寫作抱著寬容的態度,在這本書之前還有6 本書和一張CD,她不僅容忍了我的工作,而且在我最需要支持的時候,給了我鼓勵。她一直在提醒我,除了C++和軟體,生活中還有很多很多東西。

然後是我們的小狗Persephone。當我寫到這裡的時候,她已經到6 歲生日了。今天晚上,她和Nancy,還有我,將去Baskin-Robbins 吃冰淇淋。Persephone 將吃香草味的。盛上一勺,放在杯子裡,打包帶走。

相關詞條

熱門詞條

聯絡我們