條款1:理解模板類型推導
- 推導模版類型時匙赞,引用的值視為非引用佛掖,即忽略引用。
- 推導通用引用類型參數(shù)時涌庭,左值特殊處理芥被。
- 推導傳值參數(shù)時,忽略const和volatile坐榆。
+推導模版類型時拴魄,參數(shù)是數(shù)組或函數(shù)名則退化為指針,除非用來初始化引用席镀。
條款2:理解auto類型推導
- 推導auto類型一般和推導模版類型是一致的匹中,但auto類型推導對于大括號初始化{}會推導為std::initializer_list,模版類型推導則不會豪诲。
- 做為函數(shù)返回類型或lambda參數(shù)中的auto類型推導意味著模版類型推導顶捷,而不做為auto類型推導。
條款3:理解decltype.
- decltype幾乎總是推導出變量或表達式的類型屎篱,不做任何修改服赎。
- 類型為T的左值表達式而不是名稱葵蒂,decltype總是推導成引用T&。
- C++14支持decltype(auto)重虑,和auto一樣刹勃,通過初始值推導,但使用decltype規(guī)則嚎尤。
條款4:知道如何查看推導出來的類型.
- 推導出來的類型通忱笕剩可以使用IDE編輯器,編譯錯誤信息或Boost TypeIndex庫查看到芽死。
- 有些工具查看到的結果可能沒有任何幫助或者就是錯誤的乏梁,所以理解類型推導規(guī)則還是有用的。
條款5:優(yōu)先使用auto关贵,而不是顯式的類型聲明
- auto變量必須初始化遇骑,基本上不會有類型不匹配導致的可移值問題或效率問題的影響,也可以簡化重構過程揖曾,一般也比顯式指定類型需要更少的鍵盤輸入落萎。
- auto類型的變量請查看條款2和條款6中描述的陷阱。
條款6:當auto推導出的類型不是想要的類型時炭剪,使用顯示類型初始化的慣用語法练链。
- “不可見的”代理類型會導致auto對初始化表達式推導出“錯誤的”類型。
- 顯示類型初始化的慣用語法迫使auto推導出想要的類型奴拦。
條款7:創(chuàng)建對象時區(qū)分()和{}媒鼓。
- 花括號初始化方法是最為廣泛使用的初始化語法,可以防止類型轉換變窄错妖,也不受C++煩人的解析問題影響绿鸣。
- 構造函數(shù)的重載解析中,花括號初始化方法只要可能就會匹配std::initializer_list參數(shù)暂氯,即使有其它的構造函數(shù)提供了看上去更好的匹配參數(shù)潮模。
- std::vector<數(shù)字類型>在使用圓括號和花括號初始化時意義完全不同。
- 在模板中的對象創(chuàng)建時選擇圓括號還是花括號是一個挑戰(zhàn)痴施。
條款8:優(yōu)先使用nullptr擎厢,而不是0和NULL。
- 優(yōu)先使用nullptr晾剖,而不是0和NULL锉矢。
- 避免整形或指針類型的重載。
條款9:優(yōu)先使用alias聲明齿尽,而不是typedef。
- typedefs不支持模板化灯节,alias聲明支持循头。
- alias模板避免了“::type”后綴绵估,以及在模板中的用于typedefs的“typename”前綴。
- 所有C++11中類型traits轉換卡骂,C++14都提供了alias模板国裳。
條款10:優(yōu)先使用范圍枚舉,而不是非范圍枚舉全跨。
- C++98類型的枚舉就是非范圍枚舉缝左。
- 范圍枚舉的枚舉器只在枚舉內(nèi)部可見,只能通過cast轉換為其它類型浓若。
- 范圍枚舉和非范圍枚舉都支持指定底層類型渺杉。范圍枚舉的默認底層類型是int, 而非范圍沒有默認底層類型。
- 范圍枚舉總是可以前向聲明挪钓,而非范圍枚舉只有指定底層類型時才可以前向聲明是越。
條款11:優(yōu)先使用deleted函數(shù),而不是私有未定義函數(shù)碌上。
- 優(yōu)先使用deleted函數(shù)倚评,而不是私有未定義函數(shù)。
- 任何函數(shù)都可以deleted, 包括非成員函數(shù)和模板實例函數(shù)馏予。
條款12:把重寫函數(shù)聲明為override
- 把重寫函數(shù)聲明為override天梧。
- 成員函數(shù)引用限定符可以區(qū)別對待左值和右值對象。
條款13:優(yōu)先使用const_iterator, 而不是其它的iterator霞丧。
- 優(yōu)先使用const_iterator, 而不是其它的iterator腿倚。
- 大多數(shù)泛型代碼中,優(yōu)先使用非成員函數(shù)版本的begin, end, rbegin等等蚯妇,而不是相應的成員函數(shù)敷燎。
條款14:如果函數(shù)不拋出異常則聲明為noexcept
- noexcept是函數(shù)接口的一部分,意味著調用者必須依賴它箩言。
- noexcept函數(shù)更容易優(yōu)化硬贯。
- noexcept在移動操作,交換陨收,內(nèi)存釋放函數(shù)饭豹,析構函數(shù)中特別有價值。
- 大多數(shù)函數(shù)是異常中立的务漩,而不是noexcept拄衰。
條款15:只要有可能就使用constexpr。
- constexpr對象是常量并且是在編譯期初始化的饵骨。
- constexpr函數(shù)在給定的參數(shù)是編譯期已知的情況下可以生成編譯期結果翘悉。
- constexpr對象和函數(shù)比起非constexpr對象和函數(shù),可以使用在更寬泛的上下文范圍居触。
- constexpr是對象接口或函數(shù)接口的一部分妖混。
條款16:使const成員成為線程安全的老赤。
- 除非確定不會在并發(fā)環(huán)境中使用,否則const成員一定要是線程安全的制市。
- std::atomic變量比mutex性能更好抬旺,但只能用于操作單一變量或單一內(nèi)存位置。
條款17:理解特殊成員函數(shù)的生成祥楣。
- 特殊成員函數(shù)就是編譯器自動生成的:默認構造函數(shù)开财,析構函數(shù),拷貝操作误褪,移動操作责鳍。
- 移動操作函數(shù)只有在沒有顯式聲明移動操作,拷貝操作振坚,析構函數(shù)時才會生成薇搁。
- 拷貝構造函數(shù)只有在沒有顯式聲明拷貝構造函數(shù)時才會生成,而且如果聲明了移動操作就會被刪除渡八】醒螅拷貝賦值操作只有在沒有顯式聲明拷貝賦值操作符才會生成,而且聲明了移動操作就會刪除屎鳍。如果顯式聲明了析構函數(shù)宏娄,則拷貝操作就會過時。
- 成員函數(shù)模板永遠不會阻止特殊成員函數(shù)的生成逮壁。
條款18:使用std::unique_ptr管理獨占資源孵坚。
- std::unique_ptr小巧,快速窥淆,只能移動卖宠,用于管理獨占資源。
- 默認情況下資源析構使用delete, 但可以指定自定義的刪除器忧饭。有狀態(tài)的刪除器或函數(shù)指針會增加std::unique_ptr對象的大小扛伍。
- std::unique_ptr很容易轉換為std::shared_ptr。
條款19: 使用std::shared_ptr管理共享資源词裤。
- std::shared_ptrs對于隨意的資源的共享生命周期管理提供方便的垃圾回收處理方法惕澎。
- 相對于std::unique_ptr來說掠归,std::shared_ptr對象通常大一倍光羞,主要是控制塊美旧,原子引用計數(shù)操作導致。
- 默認資源的析構是通過delete, 但也支持自定義刪除器渔肩。刪除器的類型對std::shared_ptr的類型不起作用因俐。
- 避免從原始指針類型的變量生成std::shared_ptrs。
條款20:使用std::weak_ptr代替可能會發(fā)生懸空指針的std::shared_ptr。
- 使用std::weak_ptr代替可能會發(fā)生懸空指針的std::shared_ptr女揭。
- 潛在的使用std::weak_ptr的情景有緩存蚤假,觀察者列表栏饮,避免std::shared_ptr循環(huán)引用吧兔。
條款21:優(yōu)先使用std::make_unique和std::make_shared,而不是直接使用new袍嬉。
- 相對于直接使用new來說境蔼,make函數(shù)消除了源代碼重復,提升了異常安全伺通,而且std::make_shared和std::allocate_shared都會生成更小更快的代碼箍土。
- 不適合使用make函數(shù)的情況有指定自定義的刪除器,需要傳遞括號初始化器罐监。
- 對std::shared_ptrs來說吴藻,make函數(shù)不推薦使用的其它情況還有(1)有自定義內(nèi)存管理的類(2)有內(nèi)存問題的系統(tǒng),非常大的對象弓柱,以及std::weak_ptrs比std::shared_ptrs的生命還要長的情況沟堡。
條款22:如果使用Pimpl慣用法,則要在實現(xiàn)文件中定義特殊成員函數(shù)矢空。
- Pimpl慣用法通過減少類的實現(xiàn)和類的客戶的編譯依賴關系縮減了編譯時間航罗。
- 如果std::unique_ptr用于pImpl指針,則在類的頭文件中聲明特殊成員函數(shù)屁药,在實現(xiàn)文件中實現(xiàn)粥血。即使默認的函數(shù)實現(xiàn)可以接受的話也要這么做。
- 以上建議適用于std::unique_ptr酿箭,但不適用于std::shared_ptr复亏。
條款23: 理解std::move和std::forward。
- std::move無條件轉換為右值缭嫡,就其本身而言缔御,它不移動任何東西。
- std::forward只有在綁定的參數(shù)是右值時才會將參數(shù)轉換為右值械巡。
- std::move和std::forward在運行時不做任何事情刹淌。
條款24:區(qū)分通用引用和右值信引用。
- 如果函數(shù)模板參數(shù)有類型T&&并且需要推導T讥耗,或者對象聲明為auto&&, 則參數(shù)或對象是通用引用有勾。
- 如果類型聲明的格式不是精確的type&&, 或不需要類型推導,type&&就是右值引用古程。
- 通用引用如果使用右值初始化的話蔼卡,則和右值引用是一致的。如果是用左值初始化的話挣磨,則與左值引用是一致的雇逞。
條款25:對右值引用使用std::move, 對通用引用使用std::forward荤懂。
- 最后一次使用時,對右值引用使用std::move, 對通用引用使用std::forward塘砸。
- 返回值是傳值時节仿,同上面一樣。
- 如果本地對象有可能做返回值優(yōu)化的話掉蔬,永遠也不要對本地對象使用std::move或std::forward廊宪。
條款26:避免對通用引用進行重載。
- 重載通用引用幾乎總是超預期地頻繁調用了通用引用的重載女轿。
- 完美轉發(fā)構造函數(shù)特別有問題箭启,因為比non-const左值的拷貝構造函數(shù)有更好的匹配,這樣就會派生類調用基類的拷貝構造函數(shù)和移動構造函數(shù)蛉迹。
條款27:熟悉重載通用引用函數(shù)的其它替代方法
- 通用引用和重載的組合的替代方法有使用不同的函數(shù)名傅寡,通過常量的左值引用傳遞參數(shù),通過值傳遞參數(shù)北救,以及使用標記調度荐操。
- 通過std::enable_if約束模板可以允許通用引用和重載一起使用,但會控制編譯器使用通用引用重載的條件扭倾。
- 通用引用參數(shù)經(jīng)常會有提升效率的優(yōu)點淀零,但是通常也會有使用上的缺點。
條款28:理解引用折疊膛壹。
- 引用折疊發(fā)生在四種情況:模板實例化驾中,自動類型生成,typedef和alias聲明的創(chuàng)建和使用模聋,decltype肩民。
- 當編譯器在引用折疊環(huán)境中生成引用的引用時,結果就會成為單引用链方。如果原始的引用有一個是左值引用持痰,則結果就是左值引用,否則就是右值引用祟蚀。
- 通用引用是右值引用的情況有工窍,類型推導可以區(qū)分左值和右值時,以及發(fā)生引用折疊時前酿。
條款29: 要假定移動操作是不存在在患雏,不是廉價的,也不是可用的罢维。
- 要假定移動操作是不存在在淹仑,不是廉價的,也不是可用的。
- 已知類型或支持移動語義類型的代碼中匀借,不需要有假定颜阐。
條款30:熟悉完美轉發(fā)失敗案例。
- 如果模板類型推導失敗或推導出錯誤的類型時吓肋,完美轉發(fā)就會失敗凳怨。
- 導致完美轉發(fā)失敗的參數(shù)類型有括號初始化器,使用0或NULL的指針蓬坡,整形常量靜態(tài)數(shù)據(jù)成員的聲明猿棉,模板和重載函數(shù)名稱磅叛,位成員屑咳。
條款31:避免默認的捕捉模式。
- 默認的傳引用操作捕捉會導致懸空引用弊琴。
- 默認的傳值操作捕捉容易受懸空指針影響(特別是這里)兆龙,并且會誤導成lambdas是自包含的。
條款32:使用init捕捉來移動對象到閉包敲董。
- 使用c++14的init捕捉來移動對象到閉包紫皇。
- C++11中,通過手寫的類或std::bind來模仿init捕捉腋寨。
條款33:使用decltype調用std::forward移動auto&&參數(shù)聪铺。
- 使用decltype調用std::forward移動auto&&參數(shù)。
條款34:優(yōu)先使用lambdas萄窜,而不是std::bind
- Lambdas更容易閱讀铃剔,更快捷,并且比std::bind更高效查刻。
- 只有在C++11中键兜,std::bind在實現(xiàn)移動捕捉或綁定對象到模板化的函數(shù)調用操作符上可能會有用。
條款35:優(yōu)先采用基于task的編程方法穗泵,而不是基于thread(相關的類)普气。
- std::thread API從異步運行函數(shù)中得到非直接的結果,如果函數(shù)拋出異常佃延,則程序終止现诀。
- 基于Thread的編程方法調用需要手工管理線程耗盡、過度訂閱履肃、負載均衡仔沿,以及適應新平臺等問題。
- 基于Task的編程方法通過std::async使用默認加載策略來處理大多數(shù)的問題榆浓。
條款36:如果需要異步處理于未,請指定std::launch::async。
- std::async的默認加載策略既允許異步執(zhí)行,也允許同步的執(zhí)行烘浦。
- 靈活性也會導致訪問thread_local時產(chǎn)生不確定性抖坪,線程也許永遠不會執(zhí)行,也會影響基于超時等待調用的程序邏輯闷叉。
- 如果需要異步處理擦俐,請指定std::launch::async。
條款37:使std::threads在任何路徑下都是不能join的握侧。
- 使std::threads在任何路徑下都是不能join的蚯瞧。
- 在析構函數(shù)上join會導致難以調試的性能問題。
- 在析構函數(shù)上detach會導致難以調試的未定義行為品擎。
- 將std::thread對象做為數(shù)據(jù)成員列表項中的最后一個埋合。
條款38:要小心不同的線程句柄析構行為。
- Future的析構函數(shù)通常只會銷毀future的數(shù)據(jù)成員萄传。
- 涉及到非延期task的共享狀態(tài)的final future是通過std::async甚颂,在task完成后加載的。
條款39: 考慮對于一次性事件通信中使用void future秀菱。
對于簡單的事件通信振诬,基于condvar的設計需要一個多余的互斥量,對檢測和響應任務的相應進度施加限制衍菱,并且需要響應任務驗證事件是否已發(fā)生赶么。
設計使用一個標記來避免這些問題,但這是基于輪詢的脊串,而不是基于阻塞的辫呻。
condvar和標記可以一起使用,但結果通信機制會有點不自然洪规。
使用std::promises和futures來回避這些問題印屁,但是這種方法使用堆內(nèi)存來處理共享狀態(tài),并且只能用于一次性通信斩例。
條款40:使用std::atomic處理并發(fā)雄人,使用volatile處理特殊內(nèi)存。
- std::atomic用來在不使用mutex的多線程情形下訪問數(shù)據(jù)念赶。它是編寫并發(fā)軟件的一個工具础钠。
- volatile用來在讀寫操作不是優(yōu)化的方式下處理內(nèi)存。它是編寫處理特殊內(nèi)存的一個工具叉谜。
條款41: 考慮使用傳值來處理移動操作很廉價而且總是被拷貝的參數(shù)(如果移動沒有提高性能的話旗吁,編譯器可能就直接優(yōu)化成拷貝了)
- 對于能夠拷貝,移動操作很廉價而且總是被拷貝的參數(shù)停局,傳值跟傳引用基本上同樣高效很钓,而且更容易實現(xiàn)香府,產(chǎn)生更少的代碼。
- 通過構造函數(shù)拷貝參數(shù)可能比通過賦值操作符拷貝參數(shù)更加高昂码倦。
- 傳值操作會有切片問題企孩,所以一般對于基類參數(shù)類型不適合。
條款42:考慮使用emplace的函數(shù)袁稽,而不是insert函數(shù)
- 原則上勿璃,emplace的函數(shù)有時應該比插入函數(shù)效率更高,而且效率永遠不會降低推汽。
- 在實踐中补疑,以下情形(emplace的函數(shù))可能更快:(1)要添加的值是構造到容器中時,而不是賦值到容器中(2)傳遞的參數(shù)類型與容器中的類型不同(3)容器不會排斥要添加的值歹撒,因為它是重復的值莲组。
- Emplace的函數(shù)有可能執(zhí)行類型轉換,而insert的函數(shù)會排斥栈妆。