為什么要學(xué)習(xí)C++?
2009 年本書(shū)作者 Stan Lippman 先生來(lái)華參加上海祝成科技舉辦的C++技術(shù)大會(huì),他表示人們現(xiàn)在還用C++的惟一理由是其性能。相比之下,Java/C#/Python等語(yǔ)言更加易學(xué)易用并且開(kāi)發(fā)工具豐富,它們的開(kāi)發(fā)效率都高于C++。但C++目前仍然是運(yùn)行最快的語(yǔ)言[1],如果你的應(yīng)用領(lǐng)域確實(shí)在乎這個(gè)性能,那么 C++ 是不二之選。
這里略舉幾個(gè)例子[2]。對(duì)于手持設(shè)備而言,提高運(yùn)行效率意味著完成相同的任務(wù)需要更少的電能,從而延長(zhǎng)設(shè)備的操作時(shí)間,增強(qiáng)用戶體驗(yàn)。對(duì)于嵌入式[3]設(shè)備而言,提高運(yùn)行效率意味著:實(shí)現(xiàn)相同的功能可以選用較低檔的處理器和較少的存儲(chǔ)器,降低單個(gè)設(shè)備的成本;如果設(shè)備銷量大到一定的規(guī)模,可以彌補(bǔ)C++開(kāi)發(fā)的成本。對(duì)于分布式系統(tǒng)而言,提高10%的性能就意味著節(jié)約10%的機(jī)器和能源。如果系統(tǒng)大到一定的規(guī)模(數(shù)千臺(tái)服務(wù)器),值得用程序員的時(shí)間去換取機(jī)器的時(shí)間和數(shù)量,可以降低總體成本。另外,對(duì)于某些延遲敏感的應(yīng)用(游戲[4],金融交易),通常不能容忍垃圾收集(GC)帶來(lái)的不確定延時(shí),而C++可以自動(dòng)并精確地控制對(duì)象銷毀和內(nèi)存釋放時(shí)機(jī)[5]。我曾經(jīng)不止一次見(jiàn)到,出于性能原因,用C++重寫現(xiàn)有的Java或C#程序。
C++之父Bjarne Stroustrup把C++定位于偏重系統(tǒng)編程(system programming) [6]的通用程序設(shè)計(jì)語(yǔ)言,開(kāi)發(fā)信息基礎(chǔ)架構(gòu)(infrastructure)是C++的重要用途之一[7]。Herb Sutter總結(jié)道[8],C++注重運(yùn)行效率(efficiency)、靈活性(flexibility)[9]和抽象能力(abstraction),并為此付出了生產(chǎn)力(productivity)方面的代價(jià)[10]。用本書(shū)作者的話來(lái)說(shuō),C++ is about efficient programming with abstractions。C++的核心價(jià)值在于能寫出“運(yùn)行效率不打折扣的抽象[11]”。
要想發(fā)揮C++的性能優(yōu)勢(shì),程序員需要對(duì)語(yǔ)言本身及各種操作的代價(jià)有深入的了解[12],特別要避免不必要的對(duì)象創(chuàng)建[13]。例如下面這個(gè)函數(shù)如果漏寫了&,功能還是正確的,但性能將會(huì)大打折扣。編譯器和單元測(cè)試都無(wú)法幫我們查出此類錯(cuò)誤,程序員自己在編碼時(shí)須得小心在意。
inline int find_longest(const std::vector<std::string>& words)
{
// std::max_element(words.begin(), words.end(), LengthCompare());
}
在現(xiàn)代CPU體系結(jié)構(gòu)下,C++ 的性能優(yōu)勢(shì)很大程度上得益于對(duì)內(nèi)存布局(memory layout )的精確控制,從而優(yōu)化內(nèi)存訪問(wèn)的局部性[14](locality of reference)并充分利用內(nèi)存階層(memory hierarchy)提速[15],這一點(diǎn)優(yōu)勢(shì)在近期內(nèi)不會(huì)被基于GC的語(yǔ)言趕上[16]。
C++的協(xié)作性不如C、Java、Python,開(kāi)源項(xiàng)目也比這幾個(gè)語(yǔ)言少得多,因此在TIOBE語(yǔ)言流行榜中節(jié)節(jié)下滑。但是據(jù)我所知,很多企業(yè)內(nèi)部使用C++來(lái)構(gòu)建自己的分布式系統(tǒng)基礎(chǔ)架構(gòu),并且有替換Java開(kāi)源實(shí)現(xiàn)的趨勢(shì)。
學(xué)習(xí)C++只需要讀一本大部頭
C++不是特性(features)最豐富的語(yǔ)言,卻是最復(fù)雜的語(yǔ)言,諸多語(yǔ)言特性相互干擾,使其復(fù)雜度成倍增加。鑒于其學(xué)習(xí)難度和知識(shí)點(diǎn)之間的關(guān)聯(lián)性,恐怕不能用“粗粗看看語(yǔ)法,就擼起袖子開(kāi)干,邊查Google邊學(xué)習(xí)[17]”這種方式來(lái)學(xué)習(xí)C++,那樣很容易掉到陷阱里或養(yǎng)成壞的編程習(xí)慣。如果想成為專業(yè)C++開(kāi)發(fā)者,全面而深入地了解這門復(fù)雜語(yǔ)言及其標(biāo)準(zhǔn)庫(kù),你需要一本系統(tǒng)而權(quán)威的書(shū),這樣的書(shū)必定會(huì)是一本八九百頁(yè)的大部頭[18]。
兼具系統(tǒng)性和權(quán)威性[19]的C++教材有兩本,C++之父Bjarne Stroustrup的代表作《The C++ Programming Language》和Stan Lippman的這本《C++ Primer》。侯捷先生評(píng)價(jià)道:“泰山北斗已現(xiàn),又何必案牘勞形于墨瀚書(shū)海之中!這兩本書(shū)都從C++盤古開(kāi)天以來(lái),一路改版,斬將擎旗,追奔逐北,成就一生榮光[20]。”
從實(shí)用的角度,這兩本書(shū)讀一本即可,因?yàn)樗鼈兏采w的C++知識(shí)點(diǎn)相差無(wú)幾。就我個(gè)人的閱讀體驗(yàn)而言,Primer更易讀一些,我十年前深入學(xué)習(xí)C++正是用的《C++ Primer第三版》。這次借評(píng)注的機(jī)會(huì)仔細(xì)閱讀了《C++ Primer第四版》,感覺(jué)像在讀一本完全不同的新書(shū)。第四版內(nèi)容組織及文字表達(dá)比第三版進(jìn)步很多[21],第三版可謂“事無(wú)巨細(xì)、面面俱到”,第四版重點(diǎn)突出詳略得當(dāng),甚至篇幅也縮短了,這多半歸功于新加盟的作者Barbara Moo。
《C++ Primer 第四版》講什么?適合誰(shuí)讀?
這是一本C++語(yǔ)言的教程,不是編程教程。本書(shū)不講八皇后問(wèn)題、Huffman編碼、漢諾塔、約瑟夫環(huán)、大整數(shù)運(yùn)算等等經(jīng)典編程例題,本書(shū)的例子和習(xí)題往往都跟C++本身直接相關(guān)。本書(shū)的主要內(nèi)容是精解C++語(yǔ)法(syntax)與語(yǔ)意(semantics),并介紹C++標(biāo)準(zhǔn)庫(kù)的大部分內(nèi)容(含STL)。“這本書(shū)在全世界C++教學(xué)領(lǐng)域的突出和重要,已經(jīng)無(wú)須我再贅言[22]。”
本書(shū)適合C++語(yǔ)言的初學(xué)者,但不適合編程初學(xué)者。換言之,這本書(shū)可以是你的第一本C++ 書(shū),但恐怕不能作為第一本編程書(shū)。如果你不知道什么是變量、賦值、分支、條件、循環(huán)、函數(shù),你需要一本更加初級(jí)的書(shū)[23],本書(shū)第1章可用作自測(cè)題。
如果你已經(jīng)學(xué)過(guò)一門編程語(yǔ)言,并且打算成為專業(yè)C++開(kāi)發(fā)者,從《C++ Primer 第四版》入手不會(huì)讓你走彎路。值得特別說(shuō)明的是,學(xué)習(xí)本書(shū)不需要事先具備C語(yǔ)言知識(shí)。相反,這本書(shū)教你編寫真正的C++程序,而不是披著C++ 外衣的C程序。
《C++ Primer 第四版》的定位是語(yǔ)言教材,不是語(yǔ)言規(guī)格書(shū),它并沒(méi)有面面俱到地談到C++的每一個(gè)角落,而是重點(diǎn)講解C++程序員日常工作中真正有用的、必須掌握的語(yǔ)言設(shè)施和標(biāo)準(zhǔn)庫(kù)[24]。本書(shū)的作者一點(diǎn)也不炫耀自己的知識(shí)和技巧,雖然他們有十足的資本[25]。這本書(shū)用語(yǔ)非常嚴(yán)謹(jǐn)(沒(méi)有那些似是而非的比喻),用詞平和,講解細(xì)致,讀起來(lái)并不枯燥。特別是如果你已經(jīng)有一定的編程經(jīng)驗(yàn),在閱讀時(shí)不妨思考如何用C++來(lái)更好地完成以往的編程任務(wù)。
盡管本書(shū)篇幅近900頁(yè),其內(nèi)容還是十分緊湊,很多地方讀一個(gè)句子就值得寫一小段代碼去驗(yàn)證。為了節(jié)省篇幅,本書(shū)經(jīng)常修改前文代碼中的一兩行,來(lái)說(shuō)明新的知識(shí)點(diǎn),值得把每一行代碼敲到機(jī)器中去驗(yàn)證。習(xí)題當(dāng)然也不能輕易放過(guò)。
《C++ Primer 第四版》體現(xiàn)了現(xiàn)代C++教學(xué)與編程理念:在現(xiàn)成的高質(zhì)量類庫(kù)上構(gòu)建自己的程序,而不是什么都從頭自己寫。這本書(shū)在第三章介紹了string和vector這兩個(gè)常用的類,立刻就能寫出很多有用的程序。但作者不是一次性把string的上百個(gè)成員函數(shù)一一列舉,而是有選擇地講解了最常用的那幾個(gè)函數(shù)。
《C++ Primer 第四版》的代碼示例質(zhì)量很高,不是那種隨手寫的玩具代碼。第10.4.2節(jié)實(shí)現(xiàn)了帶禁用詞的單詞計(jì)數(shù),第10.6利用標(biāo)準(zhǔn)庫(kù)容器簡(jiǎn)潔地實(shí)現(xiàn)了基于倒排索引思路的文本檢索,第15.9節(jié)又用面向?qū)ο蠓椒〝U(kuò)充了文本檢索的功能,支持布爾查詢。值得一提的是,這本書(shū)講解繼承和多態(tài)時(shí)舉的例子符合Liskov替換原則,是正宗的面向?qū)ο蟆O喾矗承┙滩囊詮?fù)用基類代碼為目的,常以“人、學(xué)生、老師、教授”或“雇員、經(jīng)理、銷售、合同工”為例,這是誤用了面向?qū)ο蟮?#8220;復(fù)用”。
《C++ Primer 第四版》出版于2005年,遵循2003年的C++語(yǔ)言標(biāo)準(zhǔn)[26]。C++新標(biāo)準(zhǔn)已于2011年定案(稱為C++11),本書(shū)不涉及TR1[27]和C++11,這并不意味著這本書(shū)過(guò)時(shí)了[28]。相反,這本書(shū)里沉淀的都是當(dāng)前廣泛使用的C++編程實(shí)踐,學(xué)習(xí)它可謂正當(dāng)時(shí)。評(píng)注版也不會(huì)越俎代庖地介紹這些新內(nèi)容,但是會(huì)指出哪些語(yǔ)言設(shè)施已在新標(biāo)準(zhǔn)中廢棄,避免讀者浪費(fèi)精力。
《C++ Primer 第四版》是平臺(tái)中立的,并不針對(duì)特定的編譯器或操作系統(tǒng)。目前最主流的C++編譯器有兩個(gè), GNU G++和微軟Visual C++。實(shí)際上,這兩個(gè)編譯器陣營(yíng)基本上“模塑[29]”了C++語(yǔ)言的行為。理論上講, C++語(yǔ)言的行為是由C++標(biāo)準(zhǔn)規(guī)定的。但是 C++不像其他很多語(yǔ)言有“官方參考實(shí)現(xiàn)[30]”,因此C++的行為實(shí)際上是由語(yǔ)言標(biāo)準(zhǔn)、幾大主流編譯器、現(xiàn)有不計(jì)其數(shù)的C++產(chǎn)品代碼共同確定的,三者相互制約。C++編譯器不光要盡可能符合標(biāo)準(zhǔn),同時(shí)也要遵循目標(biāo)平臺(tái)的成文或不成文規(guī)范和約定,例如高效地利用硬件資源、兼容操作系統(tǒng)提供的C語(yǔ)言接口等等。在C++標(biāo)準(zhǔn)沒(méi)有明文規(guī)定的地方,C++編譯器也不能隨心所欲自由發(fā)揮。學(xué)習(xí)C++的要點(diǎn)之一是明白哪些行為是由標(biāo)準(zhǔn)保證的,哪些是由實(shí)現(xiàn)(軟硬件平臺(tái)和編譯器)保證的[31],哪些是編譯器自由實(shí)現(xiàn),沒(méi)有保證的;換言之,明白哪些程序行為是可依賴的。從學(xué)習(xí)的角度,我建議如果有條件不妨兩個(gè)編譯器都用[32],相互比照,避免把編譯器和平臺(tái)特定的行為誤解為C++語(yǔ)言規(guī)定的行為。盡管不是每個(gè)人都需要寫跨平臺(tái)的代碼,但也大可不必自我限定在編譯器的某個(gè)特定版本,畢竟編譯器是會(huì)升級(jí)的。
本著“練從難處練,用從易處用”的精神,我建議在命令行下編譯運(yùn)行本書(shū)的示例代碼,并盡量少用調(diào)試器。另外,值得了解C++的編譯鏈接模型[33],這樣才能不被實(shí)際開(kāi)發(fā)中遇到的編譯錯(cuò)誤或鏈接錯(cuò)誤絆住手腳。(C++不像現(xiàn)代語(yǔ)言那樣有完善的模塊(module)和包(package)設(shè)施,它從C語(yǔ)言繼承了頭文件、源文件、庫(kù)文件等古老的模塊化機(jī)制,這套機(jī)制相對(duì)較為脆弱,需要花一定時(shí)間學(xué)習(xí)規(guī)范的做法,避免誤用。)
就學(xué)習(xí)C++語(yǔ)言本身而言,我認(rèn)為有幾個(gè)練習(xí)非常值得一做。這不是“重復(fù)發(fā)明輪子”,而是必要的編程練習(xí),幫助你熟悉掌握這門語(yǔ)言。一是寫一個(gè)復(fù)數(shù)類或者大整數(shù)類[34],實(shí)現(xiàn)基本的運(yùn)算,熟悉封裝與數(shù)據(jù)抽象。二是寫一個(gè)字符串類,熟悉內(nèi)存管理與拷貝控制。三是寫一個(gè)簡(jiǎn)化的vector<T>類模板,熟悉基本的模板編程,你的這個(gè)vector應(yīng)該能放入int和string等元素類型。四是寫一個(gè)表達(dá)式計(jì)算器,實(shí)現(xiàn)一個(gè)節(jié)點(diǎn)類的繼承體系(右圖),體會(huì)面向?qū)ο缶幊獭G叭齻€(gè)練習(xí)是寫?yīng)毩⒌闹嫡Z(yǔ)義的類,第四個(gè)練習(xí)是對(duì)象語(yǔ)義,同時(shí)要考慮類與類之間的關(guān)系。
表達(dá)式計(jì)算器能把四則運(yùn)算式3+2*4解析為左圖的表達(dá)式樹(shù)[35],對(duì)根節(jié)點(diǎn)調(diào)用calculate()虛函數(shù)就能算出表達(dá)式的值。做完之后還可以再擴(kuò)充功能,比如支持三角函數(shù)和變量。


在寫完面向?qū)ο蟀娴谋磉_(dá)式樹(shù)之后,還可以略微嘗試泛型編程。比如把類的繼承體系簡(jiǎn)化為下圖,然后用BinaryNode<std::plus<double> >和BinaryNode<std:: multiplies<double> >來(lái)具現(xiàn)化BinaryNode<T>類模板,通過(guò)控制模板參數(shù)的類型來(lái)實(shí)現(xiàn)不同的運(yùn)算。

在表達(dá)式樹(shù)這個(gè)例子中,節(jié)點(diǎn)對(duì)象是動(dòng)態(tài)創(chuàng)建的,值得思考:如何才能安全地、不重不漏地釋放內(nèi)存。本書(shū)第15.8節(jié)的Handle可供參考。(C++的面向?qū)ο蠡A(chǔ)設(shè)施相對(duì)于現(xiàn)代的語(yǔ)言而言顯得很簡(jiǎn)陋,現(xiàn)在C++也不再以“支持面向?qū)ο?#8221;為賣點(diǎn)了。)
C++難學(xué)嗎?“能夠靠讀書(shū)看文章讀代碼做練習(xí)學(xué)會(huì)的東西沒(méi)什么門檻,智力正常的人只要愿意花功夫,都不難達(dá)到(不錯(cuò))的程度。[36]” C++好書(shū)很多,不過(guò)優(yōu)秀的C++開(kāi)源代碼很少,而且風(fēng)格迥異[37]。我這里按個(gè)人口味和經(jīng)驗(yàn)列幾個(gè)供讀者參考閱讀:Google的protobuf、leveldb、PCRE的C++ 封裝,我自己寫的muduo網(wǎng)絡(luò)庫(kù)。這些代碼都不長(zhǎng),功能明確,閱讀難度不大。如果有時(shí)間,還可以讀一讀Chromium中的基礎(chǔ)庫(kù)源碼。在讀Google開(kāi)源的C++代碼時(shí)要連注釋一起細(xì)讀。我不建議一開(kāi)始就讀STL或Boost的源碼,因?yàn)榫帉懲ㄓ肅++模板庫(kù)和編寫C++應(yīng)用程序的知識(shí)體系相差很大。 另外可以考慮讀一些優(yōu)秀的C或Java開(kāi)源項(xiàng)目,并思考是否可以用C++更好地實(shí)現(xiàn)或封裝之(特別是資源管理方面能否避免手動(dòng)清理)。
繼續(xù)前進(jìn)
我能夠隨手列出十幾本C++好書(shū),但是從實(shí)用角度出發(fā),這里只舉兩三本必讀的書(shū)。讀過(guò)《C++ Primer》和這幾本書(shū)之后,想必讀者已能自行識(shí)別C++圖書(shū)的優(yōu)劣,可以根據(jù)項(xiàng)目需要加以鉆研。
第一本是《Effective C++ 第三版》[38]。學(xué)習(xí)語(yǔ)法是一回事,高效地運(yùn)用這門語(yǔ)言是另一回事。C++是一個(gè)遍布陷阱的語(yǔ)言,吸取專家經(jīng)驗(yàn)尤為重要,既能快速提高眼界,又能避免重蹈覆轍。《C++ Primer》加上這本書(shū)包含的C++知識(shí)足以應(yīng)付日常應(yīng)用程序開(kāi)發(fā)。
我假定讀者一定會(huì)閱讀這本書(shū),因此在評(píng)注中不引用《Effective C++ 第三版》的任何章節(jié)。
《Effective C++ 第三版》的內(nèi)容也反映了C++用法的進(jìn)步。第二版建議“總是讓基類擁有虛析構(gòu)函數(shù)”,第三版改為“為多態(tài)基類聲明虛析構(gòu)函數(shù)”。因?yàn)樵贑++中,“繼承”不光只有面向?qū)ο筮@一種用途,即C++的繼承不一定是為了覆寫(override)基類的虛函數(shù)。第二版花了很多筆墨介紹淺拷貝與深拷貝,以及對(duì)指針成員變量的處理[39]。第三版則提議,對(duì)于多數(shù)class而言,要么直接禁用拷貝構(gòu)造函數(shù)和賦值操作符,要么通過(guò)選用合適的成員變量類型[40],使得編譯器默認(rèn)生成的這兩個(gè)成員函數(shù)就能正常工作。
什么是C++編程中最重要的編程技法(idiom)?我認(rèn)為是“用對(duì)象來(lái)管理資源”,即RAII。資源包括動(dòng)態(tài)分配的內(nèi)存[41],也包括打開(kāi)的文件、TCP網(wǎng)絡(luò)連接、數(shù)據(jù)庫(kù)連接、互斥鎖等等。借助RAII,我們可以把資源管理和對(duì)象生命期管理等同起來(lái),而對(duì)象生命期管理在現(xiàn)代C++里根本不是困難(見(jiàn)注5),只需要花幾天時(shí)間熟悉幾個(gè)智能指針[42]的基本用法即可。學(xué)會(huì)了這三招兩式,現(xiàn)代的C++程序中可以完全不寫delete,也不必為指針或內(nèi)存錯(cuò)誤操心。現(xiàn)代C++程序里出現(xiàn)資源和內(nèi)存泄漏的惟一可能是循環(huán)引用,一旦發(fā)現(xiàn),也很容易修正設(shè)計(jì)和代碼。這方面的詳細(xì)內(nèi)容請(qǐng)參考《Effective C++ 第三版》第3章資源管理。
C++是目前惟一能實(shí)現(xiàn)自動(dòng)化資源管理的語(yǔ)言,C語(yǔ)言完全靠手工釋放資源,而其他基于垃圾收集的語(yǔ)言只能自動(dòng)清理內(nèi)存,而不能自動(dòng)清理其他資源[43](網(wǎng)絡(luò)連接,數(shù)據(jù)庫(kù)連接等等)。
除了智能指針,TR1中的bind/function也十分值得投入精力去學(xué)一學(xué)[44]。讓你從一個(gè)嶄新的視角,重新審視類與類之間的關(guān)系。Stephan T. Lavavej有一套PPT介紹TR1的這幾個(gè)主要部件[45]。
第二本書(shū),如果讀者還是在校學(xué)生,已經(jīng)學(xué)過(guò)數(shù)據(jù)結(jié)構(gòu)課程[46],可以考慮讀一讀《泛型編程與STL》[47];如果已經(jīng)工作,學(xué)完《C++ Primer》立刻就要參加C++項(xiàng)目開(kāi)發(fā),那么我推薦閱讀《C++編程規(guī)范》[48]。
泛型編程有一套自己的術(shù)語(yǔ),如concept、model、refinement等等,理解這套術(shù)語(yǔ)才能閱讀泛型程序庫(kù)的文檔。即便不掌握泛型編程作為一種程序設(shè)計(jì)方法,也要掌握C++中以泛型思維設(shè)計(jì)出來(lái)的標(biāo)準(zhǔn)容器庫(kù)和算法庫(kù)(STL)。坊間面向?qū)ο蟮臅?shū)琳瑯滿目,學(xué)習(xí)機(jī)會(huì)也很多,而泛型編程只有這么一本,讀之可以開(kāi)拓視野,并且加深對(duì)STL的理解(特別是迭代器[49])和應(yīng)用。
C++模板是一種強(qiáng)大的抽象手段,我不贊同每個(gè)人都把精力花在鉆研艱深的模板語(yǔ)法和技巧。從實(shí)用角度,能在應(yīng)用程序中寫寫簡(jiǎn)單的函數(shù)模板和類模板即可(以type traits為限),不是每個(gè)人都要去寫公用的模板庫(kù)。
由于C++語(yǔ)言過(guò)于龐大復(fù)雜,我見(jiàn)過(guò)的開(kāi)發(fā)團(tuán)隊(duì)都對(duì)其剪裁使用[50]。往往團(tuán)隊(duì)越大,項(xiàng)目成立時(shí)間越早,剪裁得越厲害,也越接近C。制定一份好的編程規(guī)范相當(dāng)不容易。規(guī)范定得太緊(比如定為團(tuán)隊(duì)成員知識(shí)能力的交集),程序員束手束腳,限制了生產(chǎn)力,對(duì)程序員個(gè)人發(fā)展也不利[51]。規(guī)范定得太松(定為團(tuán)隊(duì)成員知識(shí)能力的并集),項(xiàng)目?jī)?nèi)代碼風(fēng)格迥異,學(xué)習(xí)交流協(xié)作成本上升,恐怕對(duì)生產(chǎn)力也不利。由兩位頂級(jí)專家合寫的《C++編程規(guī)范》一書(shū)可謂是現(xiàn)代C++編程規(guī)范的范本。
《C++編程規(guī)范》同時(shí)也是專家經(jīng)驗(yàn)一類的書(shū),這本書(shū)篇幅比《Effective C++ 第三版》短小,條款數(shù)目卻多了近一倍,可謂言簡(jiǎn)意賅。有的條款看了就明白,照做即可:
· 第1條,以高警告級(jí)別編譯代碼,確保編譯器無(wú)警告。
· 第31條,避免寫出依賴于函數(shù)實(shí)參求值順序的代碼。C++操作符的優(yōu)先級(jí)、結(jié)合性與表達(dá)式的求值順序是無(wú)關(guān)的。裘宗燕老師寫的《C/C++ 語(yǔ)言中表達(dá)式的求值》[52]一文對(duì)此有明確的說(shuō)明。
· 第35條,避免繼承“并非設(shè)計(jì)作為基類使用”的class。
· 第43條,明智地使用pimpl。這是編寫C++動(dòng)態(tài)鏈接庫(kù)的必備手法,可以最大限度地提高二進(jìn)制兼容性。
· 第56條,盡量提供不會(huì)失敗的swap()函數(shù)。有了swap()函數(shù),我們?cè)谧远x賦值操作符時(shí)就不必檢查自賦值了。
· 第59條,不要在頭文件中或#include之前寫using。
· 第73條,以by value方式拋出異常,以by reference方式捕捉異常。
· 第76條,優(yōu)先考慮vector,其次再選擇適當(dāng)?shù)娜萜鳌?/p>
· 第79條,容器內(nèi)只可存放value和smart pointer。
有的條款則需要相當(dāng)?shù)脑O(shè)計(jì)與編碼經(jīng)驗(yàn)才能解其中三昧:
· 第5條,為每個(gè)物體(entity)分配一個(gè)內(nèi)聚任務(wù)。
· 第6條,正確性、簡(jiǎn)單性、清晰性居首。
· 第8、9條,不要過(guò)早優(yōu)化;不要過(guò)早劣化。
· 第22條,將依賴關(guān)系最小化。避免循環(huán)依賴。
· 第32條,搞清楚你寫的是哪一種class。明白value class、base class、trait class、policy class、exception class各有其作用,寫法也不盡相同。
· 第33條,盡可能寫小型class,避免寫出大怪獸。
· 第37條,public繼承意味著可替換性。繼承非為復(fù)用,乃為被復(fù)用。
· 第57條,將class類型及其非成員函數(shù)接口放入同一個(gè)namespace。
值得一提的是,《C++編程規(guī)范》是出發(fā)點(diǎn),但不是一份終極規(guī)范。例如Google的C++編程規(guī)范[53]和LLVM編程規(guī)范[54]都明確禁用異常,這跟這本書(shū)的推薦做法正好相反。
評(píng)注版使用說(shuō)明
評(píng)注版采用大開(kāi)本印刷,在保留原書(shū)板式的前提下,對(duì)原書(shū)進(jìn)行了重新分頁(yè),評(píng)注的文字與正文左右分欄并列排版。本書(shū)已依據(jù)原書(shū)2010年第11次印刷的版本進(jìn)行了全面修訂。為了節(jié)省篇幅,原書(shū)每章末尾的小結(jié)和術(shù)語(yǔ)表還有書(shū)末的索引都沒(méi)有印在評(píng)注版中,而是做成PDF供讀者下載,這也方便讀者檢索。評(píng)注的目的是幫助初次學(xué)習(xí)C++的讀者快速深入掌握這門語(yǔ)言的核心知識(shí),澄清一些概念、比較與其他語(yǔ)言的不同、補(bǔ)充實(shí)踐中的注意事項(xiàng)等等。評(píng)注的內(nèi)容約占全書(shū)篇幅的15%,大致比例是三分評(píng)、七分注,并有一些補(bǔ)白的內(nèi)容[55]。如果讀者拿不定主意是否購(gòu)買,可以先翻一翻第5章。我在評(píng)注中不談C++11[56],但會(huì)略微涉及TR1,因?yàn)門R1已經(jīng)投入實(shí)用。
為了不打斷讀者閱讀的思路,評(píng)注中不會(huì)給URL鏈接,評(píng)注中偶爾會(huì)引用《C++編程規(guī)范》的條款,以[CCS]標(biāo)明,這些條款的標(biāo)題已在前文列出。另外評(píng)注中出現(xiàn)的soXXXXXX表示http://stackoverflow.com/questions/XXXXXX 網(wǎng)址。
網(wǎng)上資源
代碼下載:http://www.informit.com/store/product.aspx?isbn=0201721481
豆瓣頁(yè)面:http://book.douban.com/subject/10944985/
術(shù)語(yǔ)表與索引PDF下載:http://chenshuo.com/cp4/
本文電子版發(fā)布于https://github.com/chenshuo/documents/downloads/LearnCpp.pdf,方便讀者訪問(wèn)腳注中的網(wǎng)站。
我的聯(lián)系方式:giantchen_AT_gmail.com http://weibo.com/giantchen
陳碩
2012年3月
中國(guó)·香港
評(píng)注者簡(jiǎn)介 :
陳碩,北京師范大學(xué)碩士,擅長(zhǎng) C++ 多線程網(wǎng)絡(luò)編程和實(shí)時(shí)分布式系統(tǒng)架構(gòu)。現(xiàn)任職于香港某跨國(guó)金融公司 IT 部門,從事實(shí)時(shí)外匯交易系統(tǒng)開(kāi)發(fā)。編寫了開(kāi)源 C++ 網(wǎng)絡(luò)庫(kù) muduo; 參與翻譯了《代碼大全(第二版)》和《C++ 編程規(guī)范(繁體版)》;2009 年在上海 C++ 技術(shù)大會(huì)做技術(shù)演講《當(dāng)析構(gòu)函數(shù)遇到多線程》,同時(shí)擔(dān)任 Stanley Lippman 先生的口譯員;2010 年在珠三角技術(shù)沙龍做技術(shù)演講《分布式系統(tǒng)的工程化開(kāi)發(fā)方法》;2012年在“我們的開(kāi)源項(xiàng)目”深圳站做《Muduo 網(wǎng)絡(luò)庫(kù):現(xiàn)代非阻塞C++網(wǎng)絡(luò)編程》演講。
[1] 見(jiàn)編程語(yǔ)言性能對(duì)比網(wǎng)站 http://shootout.alioth.debian.org/ 和Google 員工寫的語(yǔ)言性能對(duì)比論文
https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf
[2] C++之父Bjarne Stroustrup維護(hù)的C++用戶列表:http://www2.research.att.com/~bs/applications.html
[3] 初窺C++在嵌入式系統(tǒng)中的應(yīng)用,請(qǐng)見(jiàn)http://aristeia.com/TalkNotes/MISRA_Day_2010.pdf
[4] Milo Yip在《C++強(qiáng)大背后》提到大部分游戲引擎(如Unreal/Source)及中間件(如Havok/FMOD)是C++實(shí)現(xiàn)的。http://www.cnblogs.com/miloyip/archive/2010/09/17/behind_cplusplus.html
[5] 孟巖《垃圾收集機(jī)制批判》:C++利用智能指針達(dá)成的效果是,一旦某對(duì)象不再被引用,系統(tǒng)刻不容緩,立刻回收內(nèi)存。這通常發(fā)生在關(guān)鍵任務(wù)完成后的清理(clean up)時(shí)期,不會(huì)影響關(guān)鍵任務(wù)的實(shí)時(shí)性,同時(shí),內(nèi)存里所有的對(duì)象都是有用的,絕對(duì)沒(méi)有垃圾空占內(nèi)存。http://blog.csdn.net/myan/article/details/1906
[6] 有人半開(kāi)玩笑地說(shuō)“所謂系統(tǒng)編程,就是那些CPU時(shí)間比程序員的時(shí)間更重要的工作。”
[7] 《Software Development for Infrastructure》 http://www2.research.att.com/~bs/Computer-Jan12.pdf
[8] Herb Sutter在C++ and Beyond 2011會(huì)議上的開(kāi)場(chǎng)演講《Why C++?》
http://channel9.msdn.com/posts/C-and-Beyond-2011-Herb-Sutter-Why-C
[9] 這里的靈活性指的是編譯器不阻止你干你想干的事情,比如為了追求運(yùn)行效率而實(shí)現(xiàn)即時(shí)編譯(just-in-time compilation)。
[10] 我曾向Stan Lippman介紹目前我在Linux下的工作環(huán)境(編輯器、編譯器、調(diào)試器),他表示這跟他在1970年代的工作環(huán)境相差無(wú)幾,可見(jiàn)C++在開(kāi)發(fā)工具方面的落后。另外C++的編譯運(yùn)行調(diào)試周期也比現(xiàn)代的語(yǔ)言長(zhǎng),這多少影響了工作效率。
[11] 可參考Ulrich Drepper在《Stop Underutilizing Your Computer》中舉的SIMD例子。
http://www.redhat.com/f/pdf/summit/udrepper_945_stop_underutilizing.pdf
[12]《Technical Report on C++ Performance》 http://www.open-std.org/jtc1/sc22/wg21/docs/18015.html
[13] 可參考Scott Meyers的《Effective C++ in an Embedded Environment》
http://www.artima.com/shop/effective_cpp_in_an_embedded_environment
[14] 我們知道std::list的任一位置插入是O(1)操作,而std::vector的任一位置插入是O(N)操作,但由于std::vector的元素布局更加緊湊(compact),很多時(shí)候std::vector的隨機(jī)插入性能甚至?xí)哂趕td::list。見(jiàn)http://ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf 這也佐證std::vector是首選容器。
[15] 可參考Scott Meyers的技術(shù)報(bào)告《CPU Caches and Why You Care》和任何一本現(xiàn)代的計(jì)算機(jī)體系結(jié)構(gòu)教材 http://aristeia.com/TalkNotes/ACCU2011_CPUCaches.pdf
[16] Bjarne Stroustrup有一篇論文《Abstraction and the C++ machine model》對(duì)比了C++和Java的對(duì)象內(nèi)存布局。 http://www2.research.att.com/~bs/abstraction-and-machine.pdf
[17] 語(yǔ)出孟巖《快速掌握一個(gè)語(yǔ)言最常用的50%》 http://blog.csdn.net/myan/article/details/3144661
[18] 同樣篇幅的Java/C#/Python教材可以從語(yǔ)言、標(biāo)準(zhǔn)庫(kù)一路講到多線程、網(wǎng)絡(luò)編程、圖形編程。
[19] “權(quán)威”的意思是說(shuō)你不用擔(dān)心作者講錯(cuò)了,能達(dá)到這個(gè)水準(zhǔn)的C++圖書(shū)作者全世界也屈指可數(shù)。
[20] 侯捷《大道之行也——C++ Primer 3/e譯序》 http://jjhou.boolan.com/cpp-primer-foreword.pdf
[21] Bjarne Stroustrup在《Programming--- Principles and Practice Using C++》的參考文獻(xiàn)中引用了本書(shū),并特別注明 use only the 4th edition.
[22] 侯捷《C++ Primer 4/e譯序》
[23] 如果沒(méi)有時(shí)間精讀注21中提到的那本大部頭,短小精干的《Accelerated C++》亦是上佳之選。另外如果想從C語(yǔ)言入手,我推薦裘宗燕老師的《從問(wèn)題到程序:程序設(shè)計(jì)與C語(yǔ)言引論(第2版)》
[24] 本書(shū)把iostream的格式化輸出放到附錄,徹底不談locale/facet,可謂匠心獨(dú)運(yùn)。
[25] Stan Lippman曾說(shuō):Virtual base class support wanders off into the Byzantine... The material is simply too esoteric to warrant discussion...
[26] 基本等同于1998年的初版C++標(biāo)準(zhǔn),修正了編譯器作者關(guān)心的一些問(wèn)題,與普通程序員基本無(wú)關(guān)。
[27] TR1是2005年C++標(biāo)準(zhǔn)庫(kù)的一次擴(kuò)充,增加了智能指針、bind/function、哈希表、正則表達(dá)式等。
[28]作者正在編寫《C++ Primer 第五版》,會(huì)包含C++11的內(nèi)容。
[29] G++統(tǒng)治了Linux平臺(tái),并且能用在很多Unix平臺(tái)上;Visual C++統(tǒng)治了Windows平臺(tái)。其他C++編譯器的行為通常要向它們靠攏,例如Intel C++在Linux上要兼容G++,而在Windows上要兼容Visual C++。
[30] 曾經(jīng)是Cfront,本書(shū)作者正是其主要開(kāi)發(fā)者。http://www.softwarepreservation.org/projects/c_plus_plus
[31] 包括C++標(biāo)準(zhǔn)有規(guī)定,但編譯器拒絕遵循的。http://stackoverflow.com/questions/3931312/value-initialization-and-non-pod-types
[32] G++ 是免費(fèi)的,可使用較新的4.x版,最好32-bit和64-bit一起用,因?yàn)榉?wù)端已經(jīng)普及64位。微軟也有免費(fèi)的編譯器,可考慮Visual C++ 2010 Express,建議不要用老掉牙的Visual C++ 6.0作為學(xué)習(xí)平臺(tái)。
[33] 可參考陳碩寫的《C++工程實(shí)踐經(jīng)驗(yàn)談》中的“C++編譯模型精要”一節(jié)。
[34] 大整數(shù)類可以以std::vector<int>為成員變量,避免手動(dòng)資源管理。
[35] “解析”可以用數(shù)據(jù)結(jié)構(gòu)課程介紹的逆波蘭表達(dá)式方法,也可以用編譯原理中介紹的遞歸下降法,還可以用專門的Packrat算法。可參考http://www.relisoft.com/book/lang/poly/3tree.html
[36] 孟巖《技術(shù)路線的選擇重要但不具有決定性》 http://blog.csdn.net/myan/article/details/3247071
[37] 從代碼風(fēng)格上往往能判斷項(xiàng)目成型的時(shí)代。
[38] Scott Meyers著,侯捷譯,電子工業(yè)出版社。
[39] Andrew Koenig的《Teaching C++ Badly: Introduce Constructors and Destructors at the Same Time》
http://drdobbs.com/blogs/cpp/229500116
[40] 能自動(dòng)管理資源的string、vector、shared_ptr等等,這樣多數(shù)class連析構(gòu)函數(shù)都不必寫。
[41] “分配內(nèi)存”包括在堆(heap)上創(chuàng)建對(duì)象。
[42] 包括TR1中的shared_ptr、weak_ptr,還有更簡(jiǎn)單的boost::scoped_ptr。
[43] Java 7有try-with-resources語(yǔ)句,Python有with語(yǔ)句,C#有using語(yǔ)句,可以自動(dòng)清理?xiàng)I系馁Y源,但對(duì)生命期大于局部作用域的資源無(wú)能為力,需要程序員手工管理。
[44] 孟巖《function/bind的救贖(上)》http://blog.csdn.net/myan/article/details/5928531
[45] http://blogs.msdn.com/b/vcblog/archive/2008/02/22/tr1-slide-decks.aspx
[46] 最好再學(xué)一點(diǎn)基礎(chǔ)的離散數(shù)學(xué)。
[47] Matthew Austern著,侯捷譯,中國(guó)電力出版社。
[48] Herb Sutter等著,劉基誠(chéng)譯,人民郵電出版社。(這本書(shū)繁體版由侯捷先生和我翻譯。)
[49] 侯捷先生的《芝麻開(kāi)門:從Iterator談起》 http://jjhou.boolan.com/programmer-3-traits.pdf
[50] 孟巖《編程語(yǔ)言的層次觀點(diǎn)——兼談C++的剪裁方案》 http://blog.csdn.net/myan/article/details/1920
[51] 一個(gè)人通常不會(huì)在一個(gè)團(tuán)隊(duì)工作一輩子,其他團(tuán)隊(duì)可能有不同的C++剪裁使用方式,程序員要有“一桶水”的本事,才能應(yīng)付不同形狀大小的水碗。
[52] http://www.math.pku.edu.cn/teachers/qiuzy/technotes/expression2009.pdf
[53] http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions
[54] http://llvm.org/docs/CodingStandards.html#ci_rtti_exceptions
[55] 第10章繪制了數(shù)據(jù)結(jié)構(gòu)示意圖,第11章補(bǔ)充lower_bound 和 upper_bound的示例。
[56] 從Scott Meyers的講義可以快速學(xué)習(xí)C++11 http://www.artima.com/shop/overview_of_the_new_cpp