《Effective Modern C++》
22:如果使用Pimpl慣用法,則要在實現文件中定義特殊成員函數
- Pimpl慣用法通過減少類的實現和類的客戶端的編譯依賴關係縮減了編譯時間。
- 如果std::unique_ptr用於pImpl指針,則在類的頭文件中聲明特殊成員函數,在實現文件中實現。即使默認的函數實現可以接收的話也可以這麼做。
- 以上建議適用於std::unique_ptr,但不適用於是std::shared_ptr。
23:理解std::move和std::forword
- std::move無條件轉換爲右值,就是本省而言,它不移動任何東西。
- std::forward只有在綁定的參數是右值時纔會將參數轉換爲右值。
- std::move和std::forward在運行時不做任何事情。
24:區分通用引用和右值引用
- 如果函數模板參數有類信託T&&並且需要推導T,或者對象聲明爲auto&&,則參數或對象是通用引用。
- 如果類型聲明的格式不是精確的type&&,或不需要類型推導,type&&就是右值引用。
- 通用引用如果使用右值初始化的話,則和右值引用是一致的。如果是用左值初始化的話,則與左值引用是一致的。
25:對右值引用使用std::move,對通用引用使用std::froward.
- 最後一次使用時,對右值引用使用std:::move,對通用引用使用std::forward。
- 返回值是傳值時,同上面一樣。
- 如果本地對象有可能做返回值優化的話,永遠也不要對本地對象使用std::move或std::forward。
26:避免對通用引用進行重載
- 重載通用引用幾乎總是超預期地頻繁調用了通用引用的重載。
- 完美轉發構造函數特別有問題,因爲比non-const左值的拷貝構造函數有更好的匹配,這樣就會派生類調用基類的拷貝構造函數和移動構造函數。
27:熟悉重載通用引用函數的其他方法
- 通用引用和重載的組合替代方法有使用不同的函數名,通過常量的左值引用傳遞參數,通過值傳遞參數,以及使用標記調度。
- 通過std::enable_if約束模板可以允許通用引用和重載一起使用,但會控制編譯器使用通用引用重載的條件。
- 通用引用參數經常會有提升效率的優點,但是通常也會有使用上的缺點。
28:理解引用摺疊
- 引用摺疊發生在四種情況:模板實例化、自動類型生成、typedef和alias聲明的創建和使用,和decltype
- 當編譯器在引用摺疊環境中生成引用的引用時,結果就會成爲單引用。如果原始的引用有一個是左值引用,則結果就是左值引用,否則就是右值引用。
- 通用引用是右值引用的情況有,類型推導可以區分左值和右值時,以及發生引用摺疊。
29:假定移動操作存在,不是廉價的,也不是可用的
- 假定移動操作存在,不是廉價的,也不是可用的。
- 已知類型或支持移動語義類型的代碼中,不需要有待定。
30:熟悉完美轉發失敗案例
- 如果模板類型推導失敗或推導出錯誤的類型時,完美轉發就會失敗。
- 導致完美轉發失敗的參數類型有括號初始化器,使用0或NULL的指針,整型變量靜態數據成員的聲明,模板和重載函數名稱,位成員。
31:避免默認的捕捉模式
- 默認的傳引用操作捕捉會導致懸空引用。
- 默認的傳值操作捕捉容易受懸空指針影響,並且會誤導成lambdas是自包含的。
32:使用init捕捉來移動對象到閉包
- 使用C++14的init捕捉來移動對象到閉包。
- C++11中,通過手寫的類或std::bind來模仿init捕捉。
33:使用decltype調用std::forward移動auto&&參數
- 使用decltype調用std::forward移動auto&&參數。
34:優先使用lambdas,而不是std::bind
- Lambdas更容易閱讀,更快捷,並且比std::bind更高效。
- 只有在 C++11中,std::bind在實現移動捕捉或綁定對象到模板化的函數調用操作符上可能會有用。
35:優先使用基於task的編程方法,而不是基於thread(相關的類)
- std::thread API從異步運行函數中得到非直接的結果,如果函數拋出異常,則程序終止。
- 基於Thread的編程方法調用需要手工管理線程耗盡、過度訂閱、負載均衡,以及適應新平臺等問題。
- 基於Task的編程方法通過std::async使用默認加載策略來處理大多數的問題。
36:如果需要異步處理,請指定std::launch::async
- std::async的默認加載策略既允許異步執行,也允許同步的執行。
- 靈活性也會導致訪問thread_local時產生不確定性,線程也許永遠不會執行,也會影響基於超時等待調用的程序邏輯。
- 如果需要異步處理,請指定std::launch::async。
37:使std::threads在任何路徑下都是不能join的
- 使std::threads在任何路徑下都是不能join的。
- 在析構函數上join會導致難以調試的性能問題。
- 在析構函數上detach會導致難以調試的未定義行爲。
- 將std::thread對象作爲數據成員列表項中的最後一個。
38:要小心不同的線程句柄析構行爲
- Future的析構函數通常只會銷燬future的數據成員。
- 涉及到非延期task的共享狀態的final future是通過std::async,在task完成後加載的。
39:考慮對於一次性事件通信中使用void future
- 對於簡單的事件通信,基於condvar的設計需要一個多餘的互斥量,對檢測和響應任務的相應進度施加限制,並且需要任務驗證事件是否已發生。
- 設計使用一個標記來避免這些問題,但這是基於輪詢的,而不是基於阻塞的。
- condvar和標記可以一起使用,但結果通信機制會有點不自然。
- 使用std::promises和futures來回避這些問題,但是這種方法使用堆內存來處理共享狀態,並且只能用於一次性通信。
40:使用std::atomic處理併發,使用volatile處理特殊內存
- std::atomic用來在不使用mutex的多線程情形下訪問數據。它是編寫併發軟件的一個工具。
- volatile用來在讀寫操作不是優化的方式下處理內存。它是編寫處理特殊內存的一個工具。
41:考慮使用傳值來處理移動操作很廉價而且總是被拷貝的參數
- 對於能夠拷貝,移動操作很廉價而且總是被拷貝的參數,傳值跟傳引用基本上同樣高效,而且更容易實現,產生更少的代碼。
- 通過構造函數拷貝參數可能比通過複製操作符拷貝參數更加高昂。
- 傳值操作會有切片問題,所以一般對於基類參數類型不適合。
42:考慮使用emplace的函數,而不是insert函數
- 原則上,emplace的函數有時應該比插入函數效率更高,而且效率永遠不會降低。
- 在實踐中,以下情形可能更快:(1)要添加的值是構造到容器中時,而不是賦值到容器中(2)傳遞的參數類型與容器中的類型不同(3)容器不會排斥要添加的值,因爲它是重要的值。
- Emplace的函數有可能執行類型轉換,而insert的函數會排斥。