這本書(shū)屬于“想提高必看之書(shū)”,相見(jiàn)恨晚,建議所有C++程序員都看看欺冀,沒(méi)事也可以拿出來(lái)翻翻。大家也可以瀏覽下面的筆記看看是不是所有條款都了解了萨脑。
我已經(jīng)將這個(gè)筆記的思維導(dǎo)圖和有用的代碼片段上傳到我的GitHub上了隐轩,歡迎大家下載。
讓自己習(xí)慣C++
- 視C++為一個(gè)語(yǔ)言聯(lián)邦
- C(C++的基礎(chǔ)C語(yǔ)言的部分)
- Object-Oriented C++(面向?qū)ο螅?/li>
- Template C++(泛型編程)
- STL(標(biāo)準(zhǔn)庫(kù))
- C++高效編程守則視狀況而變化渤早,取決于你使用C++的哪一部分职车。
盡量以const,enum鹊杖,inline替換#define
- 對(duì)于單純常量悴灵,最好以const對(duì)象或enums替換#define。
- 對(duì)于形似函數(shù)的宏骂蓖,最好改用inline函數(shù)替換#define积瞒。
盡可能使用const
- 將某些東西聲明為const可幫助編譯器偵測(cè)出錯(cuò)誤用法。const可被施加于任何作用域內(nèi)的對(duì)象登下、函數(shù)參數(shù)茫孔、函數(shù)返回類型、成員函數(shù)本體被芳。
- 編譯器強(qiáng)制實(shí)施bitwise constness缰贝,但你編寫程序時(shí)應(yīng)該使用“概念上的常量性”(conceptual constness)。
- 當(dāng)const和non-const成員函數(shù)有著實(shí)質(zhì)等價(jià)的實(shí)現(xiàn)時(shí)畔濒,令non-const版本調(diào)用const版本可避免代碼重復(fù)剩晴。
確定對(duì)象被使用前已被初始化
- 為內(nèi)置型對(duì)象進(jìn)行手工初始化,因?yàn)镃++不保證初始化它們侵状。
- 構(gòu)造函數(shù)最好使用成員初值列赞弥,而不要在構(gòu)造函數(shù)本體內(nèi)使用賦值操作。初值列列出的成員變量壹将,其排列次序應(yīng)該和它們?cè)赾lass中的聲明次序相同嗤攻。
- 為免除“跨編譯單元之初始化次序”問(wèn)題,請(qǐng)以local static對(duì)象替換non-local static對(duì)象
了解C++默默編寫并調(diào)用哪些函數(shù)
- 編譯器可以暗自為class創(chuàng)建default構(gòu)造函數(shù)诽俯,copy構(gòu)造函數(shù),copy assignment操作符,以及析構(gòu)函數(shù)暴区。
若不想使用編譯器自動(dòng)生成的函數(shù)闯团,就該明確拒絕
- 為駁回編譯器自動(dòng)提供的機(jī)能,可將相應(yīng)的成員函數(shù)聲明為private并且不予實(shí)現(xiàn)仙粱。使用想U(xiǎn)ncopyable這樣的base class也是一種做法房交。
為多態(tài)基類聲明virtual析構(gòu)函數(shù)
- 只有當(dāng)class內(nèi)含至少一個(gè)virtual函數(shù),才為它聲明virtual析構(gòu)函數(shù)伐割。
- 為你希望它成為抽象的那個(gè)class聲明一個(gè)pure virtual析構(gòu)函數(shù)候味,且必須定義。
別讓異常逃離析構(gòu)函數(shù)
- 析構(gòu)函數(shù)絕對(duì)不要吐出異常隔心。如果一個(gè)被析構(gòu)函數(shù)調(diào)用的函數(shù)可能拋出異常白群,析構(gòu)函數(shù)應(yīng)該捕捉任何異常,然后吞下它們或者結(jié)束程序硬霍。
- 如果客戶需要對(duì)某個(gè)操作函數(shù)運(yùn)行期間拋出的異常做出反應(yīng)帜慢,那么class應(yīng)該提供一個(gè)普通函數(shù)執(zhí)行該操作。
絕不在構(gòu)造和析構(gòu)過(guò)程中調(diào)用virtual函數(shù)
- 因?yàn)樵赽ase class構(gòu)造期間唯卖,virtual函數(shù)不是virtual函數(shù)粱玲。
令operator=返回一個(gè)reference to *this
在operator=中處理“自我賦值”
- 確保當(dāng)對(duì)象自我賦值時(shí)operator=有良好行為。其中技術(shù)包括比較“來(lái)源對(duì)象”和“目標(biāo)對(duì)象”的地址拜轨、精心周到的語(yǔ)句順序抽减、以及copy-and-swap。
- 確定任何函數(shù)如果操作一個(gè)以上的對(duì)象橄碾,而其中多個(gè)對(duì)象是同一個(gè)對(duì)象時(shí)卵沉,其行為仍然正確。
復(fù)制對(duì)象時(shí)勿忘其每一個(gè)成分
- Copying函數(shù)應(yīng)該確保復(fù)制“對(duì)象內(nèi)的所有成員變量”及“所有base class成分”堪嫂。
- 不要嘗試以某個(gè)copying函數(shù)實(shí)現(xiàn)另一個(gè)copying函數(shù)偎箫。應(yīng)該將共同機(jī)能放進(jìn)第三個(gè)函數(shù)中,并由兩個(gè)copying函數(shù)共同調(diào)用皆串。
以對(duì)象管理資源
- shared_ptr
- unique_ptr
在資源管理類中小心copying行為
- 復(fù)制RAII對(duì)象必須一并復(fù)制它所管理的資源淹办,所以資源的copying行為決定RAII對(duì)象的copying行為。
- 普遍而常見(jiàn)的RAII class copying行為是:抑制copying恶复,施行引用計(jì)數(shù)法怜森。不過(guò)其他行為也都可能被實(shí)現(xiàn)。
在資源管理類中提供對(duì)原始資源的訪問(wèn)
- APIs往往要求訪問(wèn)原始資源谤牡,所以每一個(gè)RAII class應(yīng)該提供一個(gè)“取得其所管理之資源”的辦法副硅。
- 對(duì)原始資源的訪問(wèn)可能經(jīng)由顯式轉(zhuǎn)換或隱式轉(zhuǎn)換。一般而言顯式轉(zhuǎn)換比較安全翅萤,但隱式轉(zhuǎn)換對(duì)客戶比較方便恐疲。
成對(duì)使用new和delete時(shí)要采取相同形式
以獨(dú)立語(yǔ)句將newed對(duì)象置于智能指針
- 如果不這樣做,一旦異常被拋出,有可能導(dǎo)致難以察覺(jué)的資源泄露培己。
讓接口容易被正確使用碳蛋,不易被誤用
- 好的接口很容易被正確使用,不容易被誤用省咨。你應(yīng)該在你的所有接口中努力達(dá)成這些性質(zhì)肃弟。
- “促進(jìn)正確使用”的辦法包括接口的一致性,以及與內(nèi)置類型的行為兼容零蓉。
- “阻止誤用”的辦法包括建立新類型笤受、限制類型上的操作,束縛對(duì)象值敌蜂,以及消除客戶的資源管理責(zé)任箩兽。
設(shè)計(jì)class猶如設(shè)計(jì)type
寧以pass-by-reference-to-const替換pass-by-value
- 盡量以pass-by-reference-to-const替換pass-by-value。前者通常比較高效紊册,并可避免切割問(wèn)題比肄。
- 以上規(guī)則并不適用于內(nèi)置類型,以及STL的迭代器和函數(shù)對(duì)象囊陡。對(duì)它們而言芳绩,pass-by-value往往比較適當(dāng)。
必須返回對(duì)象時(shí)撞反,別妄想返回其reference
- 絕不要返回pointer或reference指向一個(gè)local stack對(duì)象妥色,或返回reference指向一個(gè)heap-allocated對(duì)象,或返回pointer或reference指向一個(gè)local static對(duì)象而又可能同時(shí)需要對(duì)個(gè)這樣的對(duì)象(單例除外)遏片。
將成員變量聲明為private
- 切記將成員變量聲明為private嘹害。這可賦予客戶訪問(wèn)數(shù)據(jù)的一致性、可細(xì)微劃分訪問(wèn)控制吮便、允許約束條件獲得保證笔呀,并提供class作者以充分的實(shí)現(xiàn)彈性。
- protect并不比public更具封裝性髓需。
寧以non-member许师、non-friend替換member函數(shù)
- 將所有便利函數(shù)放在多個(gè)頭文件內(nèi)但隸屬同一個(gè)命名空間,意味著客戶可以輕松擴(kuò)展這一組便利函數(shù)僚匆。他們需要做的就是添加更多non-member non-friend函數(shù)到此命名空間內(nèi)微渠。
- 這樣做可以增加封裝性、包裹彈性和技能擴(kuò)充性咧擂。
若所有參數(shù)皆需類型轉(zhuǎn)換逞盆,請(qǐng)為此采用non-member函數(shù)
考慮寫出一個(gè)不拋異常的swap函數(shù)
- 當(dāng)std::swap對(duì)你的類型效率不高時(shí),提供一個(gè)swap成員函數(shù)松申,并確定這個(gè)函數(shù)不拋出異常云芦。
- 如果你提供一個(gè)member swap俯逾,也該提供一個(gè)non-member swap用來(lái)調(diào)用前者。對(duì)于classes(而非templates)焕数,也請(qǐng)?zhí)鼗痵td::swap纱昧。
盡可能延后變量定義式的出現(xiàn)時(shí)間
- 你不只應(yīng)該延后變量的定義刨啸,直到非得使用該變量的前一刻為止堡赔,甚至應(yīng)該嘗試延后這份定義直到能夠給它初值實(shí)參為止。
盡量少做轉(zhuǎn)型動(dòng)作
- 如果可以设联,盡量避免轉(zhuǎn)型善已,特別是在注重效率的代碼中避免dynamic_casts。如果有個(gè)設(shè)計(jì)需要轉(zhuǎn)型動(dòng)作离例,試著發(fā)展無(wú)需轉(zhuǎn)型的替代設(shè)計(jì)换团。
- 如果轉(zhuǎn)型是必要的,試著將它隱藏于某個(gè)函數(shù)背后宫蛆∷野客戶隨后可以調(diào)用該函數(shù),而不需將轉(zhuǎn)型放進(jìn)他們自己的代碼內(nèi)耀盗。
- 寧可使用C++_style(新式)轉(zhuǎn)型想虎,不要使用舊式轉(zhuǎn)型。前者很容易辨識(shí)出來(lái)叛拷,而且也比較有著分門別類的職掌舌厨。
避免返回handles指向?qū)ο髢?nèi)部成分
- 避免返回handles(包括references、指針忿薇、迭代器)指向?qū)ο髢?nèi)部裙椭。遵守這個(gè)條款可增加封裝性,幫助const成員函數(shù)的行為像個(gè)const署浩,并將發(fā)生“虛吊號(hào)碼牌”(dangling handles)的可能性降至最低揉燃。
為“異常安全”而努力是值得的
- 異常安全函數(shù)即使發(fā)生異常也不會(huì)泄露資源或允許任何數(shù)據(jù)結(jié)構(gòu)敗壞。這樣的函數(shù)區(qū)分為三種可能的保證:基本型筋栋、強(qiáng)烈型炊汤、不拋異常型。
- “強(qiáng)烈保證”往往能夠以copy-and-swap實(shí)現(xiàn)出來(lái)二汛,但“強(qiáng)烈保證”并非對(duì)所有函數(shù)都可實(shí)現(xiàn)或具備現(xiàn)實(shí)意義婿崭。
- 函數(shù)提供的“異常安全保證”通常最高只等于其所調(diào)用之各個(gè)函數(shù)的“異常安全保證”中最弱者。
透徹了解inlining的里里外外
- 平均而言一個(gè)程序往往將80%的執(zhí)行時(shí)間花費(fèi)在20%的代碼上頭肴颊。作為一個(gè)軟件開(kāi)發(fā)者氓栈,你的目標(biāo)是找出這可以有效增進(jìn)程序整體銷量的20%代碼,然后將它inlining或竭盡所能地將它瘦身婿着。
- 將大多數(shù)inlining限制在小型授瘦、被頻繁調(diào)用的函數(shù)身上醋界。這可使日后的調(diào)試過(guò)程和二進(jìn)制升級(jí)更容易,也可使?jié)撛诘拇a膨脹問(wèn)題最小化提完,使程序的速度提升機(jī)會(huì)最大化形纺。
- 不要只是因?yàn)閒unction templates出現(xiàn)在頭文件,就將它們聲明為inline徒欣。
將文件間的編譯依存關(guān)系降至最低
- 支持“編譯依存性最小化”的一半構(gòu)想是:相依與聲明式逐样,不要相依與定義式〈蚋危基于此構(gòu)想的兩個(gè)手段是Handle classes和Interface classes脂新。
- 程序庫(kù)頭文件應(yīng)該以”完全且僅有聲明式“的形式存在。這種做法不論是否涉及templates都適用粗梭。
確定你的public繼承塑模出is-a關(guān)系
- “public繼承”以為is-a争便。適用于base classes 身上的每一件事情一定也適用于derived classes身上,因?yàn)槊恳粋€(gè)derived class對(duì)象也都是一個(gè)base class對(duì)象断医。
避免遮掩繼承而來(lái)的名稱
- derived classes內(nèi)的名稱會(huì)遮掩base classes內(nèi)的名稱滞乙。在public繼承下從來(lái)沒(méi)有人希望如此。
- 為了讓被遮掩的名稱再見(jiàn)天日鉴嗤,可使用using聲明式或轉(zhuǎn)角函數(shù)斩启。
區(qū)分接口繼承和實(shí)現(xiàn)繼承
- pure virtual函數(shù)只具體指定接口繼承。
- 簡(jiǎn)樸的(非純)impure virtual函數(shù)具體指定接口繼承及缺省實(shí)現(xiàn)繼承躬窜。
- non-virtual函數(shù)具體指定接口繼承以及強(qiáng)制性實(shí)現(xiàn)繼承浇垦。
考慮virtual函數(shù)以外的其他選擇
- 使用non-virtual interface(NVI)手法
- 將virtual函數(shù)替換為“函數(shù)指針成員變量”
- 以tr1::function成員變量替換virtual函數(shù)
- 將繼承體系內(nèi)的virtual函數(shù)替換為另一個(gè)繼承體系內(nèi)的virtual函數(shù)
絕不重新定義繼承而來(lái)的non—virtual函數(shù)
絕不重新定義繼承而來(lái)的缺省參數(shù)值
- 因?yàn)槿笔?shù)值都是靜態(tài)綁定,而virtual函數(shù)——你唯一應(yīng)該覆寫的東西——卻是動(dòng)態(tài)綁定荣挨。
通過(guò)復(fù)合塑模出has-a或“根據(jù)某物實(shí)現(xiàn)出”
- 復(fù)合(composition)的意義和public繼承完全不同男韧。
- 在應(yīng)用域,復(fù)合意味has-a(有一個(gè))默垄。在實(shí)現(xiàn)域此虑。復(fù)合意味is-implemented-in-terms-of(根據(jù)某物實(shí)現(xiàn)出)。
明智而審慎地使用private繼承
- Private繼承意味is-implemented-in-terms of(根據(jù)某物實(shí)現(xiàn)出)口锭。它通常比復(fù)合的級(jí)別低朦前。但是當(dāng)derived class需要訪問(wèn)protected base class的成員,或需要重新定義繼承而來(lái)的virtual函數(shù)時(shí)鹃操,這么設(shè)計(jì)師合理的韭寸。
- 和復(fù)合不同,private繼承可以造成empty base最優(yōu)化荆隘。這對(duì)致力于“對(duì)象尺寸最小化”的程序庫(kù)開(kāi)發(fā)者而言恩伺,可能很重要。
明智而審慎地使用多重繼承
- 多重繼承比單一繼承復(fù)雜椰拒,它可能導(dǎo)致新的歧義性晶渠,以及對(duì)virtual繼承的需要凰荚。
- virtual繼承會(huì)增加大小、速度褒脯、初始化(及賦值)復(fù)雜度等等成本便瑟。如果virtual base classes不帶任何數(shù)據(jù),僵尸最具有實(shí)用價(jià)值的情況番川。
- 多重繼承的確有正當(dāng)用途到涂。其中一個(gè)情節(jié)設(shè)計(jì)“public繼承某個(gè)Interface class”和“private繼承某個(gè)協(xié)助實(shí)現(xiàn)的class”的兩相組合。
了解隱式接口和編譯器多態(tài)
- 對(duì)template參數(shù)而言爽彤,接口是隱式的养盗,奠基于有效表達(dá)式。多態(tài)則是通過(guò)template據(jù)具現(xiàn)化和函數(shù)重載解析發(fā)生于編譯器适篙。
了解typename的雙重意義
- 聲明template參數(shù)時(shí),前綴關(guān)鍵字class和typename可互換箫爷。
- 請(qǐng)使用關(guān)鍵字typename標(biāo)識(shí)嵌套從屬類型名稱:但不得在base class lists(基類列)或member initialization list(成員初值列)內(nèi)以它作為base class修飾符嚷节。
學(xué)習(xí)處理模板化基類內(nèi)的名稱
- 可在derived class templates內(nèi)通過(guò)“this->”指涉base class template內(nèi)的成員名稱,或藉由一個(gè)明白寫出的“base class資格修飾符”完成虎锚。
將于參數(shù)無(wú)關(guān)的代碼抽離templates
- templates生成多個(gè)classes和多個(gè)函數(shù)硫痰,所以任何template代碼都以應(yīng)該與某個(gè)造成膨脹的template參數(shù)產(chǎn)生相依關(guān)系。
- 因費(fèi)類型模板參數(shù)而造成的代碼膨脹窜护,往往可消除效斑,做法是以函數(shù)參數(shù)或class成員變量替換template參數(shù)。
- 因類型參數(shù)而造成的代碼膨脹柱徙,往往可降低缓屠,做法是讓帶有完全相同二進(jìn)制表述的具現(xiàn)類型共享實(shí)現(xiàn)碼。
運(yùn)用成員函數(shù)模板接受所有兼容類型
- 請(qǐng)使用member function template生成“可接受所有兼容類型”的函數(shù)护侮。
- 如果你聲明member templates用于“泛化copy構(gòu)造”或“泛化assignment操作”敌完,你還是需要聲明正常的copy構(gòu)造函數(shù)和copy assignment操作符。
需要類型轉(zhuǎn)換時(shí)請(qǐng)為模板 定義非成員函數(shù)
- 當(dāng)我們編寫一個(gè)class template羊初,而它所提供之“與此template相關(guān)的”函數(shù)支持“所有參數(shù)之隱身類型轉(zhuǎn)換”時(shí)滨溉,請(qǐng)將那些函數(shù)定義為“class template內(nèi)部的friend函數(shù)”。
請(qǐng)使用trait classes表現(xiàn)類型信息
- Traits classes使得“類型相關(guān)信息”在編譯期可用长赞。它們以templates和“templates特化”完成實(shí)現(xiàn)晦攒。
- 整合重載技術(shù)后募逞,trait classes有可能在編譯期對(duì)類型執(zhí)行if...else測(cè)試会傲。
認(rèn)識(shí)template元編程
- Template metaprogramming(TMP吩坝,模板元編程)可將工作由運(yùn)行期移往編譯期亡哄,因而得以實(shí)現(xiàn)早起錯(cuò)誤偵測(cè)和更高的執(zhí)行效率糊渊。
- TMP可被用來(lái)生成“基于政策選擇組合”的客戶定制代碼,也可用來(lái)避免生成對(duì)某些特殊類型并不適合的代碼介粘。
了解new-handler的行為
- set_new_handler允許客戶指定一個(gè)函數(shù)喷鸽,在內(nèi)存分配無(wú)法獲得滿足時(shí)被調(diào)用。
- Nothrow new是一個(gè)頗為局限的工具讼庇,因?yàn)樗贿m合于內(nèi)存分配绎巨;后繼的構(gòu)造函數(shù)調(diào)用還是可能拋出異常。
了解new和delete的合理替換時(shí)機(jī)
- 有許多理由需要寫個(gè)自定的new和delete蠕啄,包括改善效能场勤、對(duì)heap運(yùn)用錯(cuò)誤進(jìn)行調(diào)試、收集heap使用信息歼跟。
編寫new和delete時(shí)需固守常規(guī)
- operator new應(yīng)該內(nèi)含一個(gè)無(wú)窮循環(huán)和媳,并在其中嘗試分配內(nèi)存,如果它無(wú)法滿足內(nèi)存需求哈街,就該調(diào)用new-handler留瞳。它也應(yīng)該有能力處理0bytes申請(qǐng)。Class專屬版本則應(yīng)該處理“比正確大小更大的(錯(cuò)誤)申請(qǐng)”骚秦。
- operator delete應(yīng)該在收到null指針時(shí)不做任何事她倘。Class專屬版本則還應(yīng)該處理“比正確大小更大的(錯(cuò)誤)申請(qǐng)”。
寫了placement new也要寫placement delete
- 當(dāng)你寫一個(gè)placement operator new作箍,請(qǐng)確定也寫出了對(duì)應(yīng)的placement operator delete硬梁。如果沒(méi)有這樣做,你的程序可能會(huì)發(fā)生隱微而時(shí)斷時(shí)續(xù)的內(nèi)存泄露胞得。
- 當(dāng)你聲明placement new和placement delete荧止,請(qǐng)確定不要無(wú)意識(shí)(非故意)地遮掩了它們的正常版本。