1. 程式人生 > >Effective Modern C++筆記彙總

Effective Modern C++筆記彙總

從 2016 年 8 月份開始讀這本書,限於目前大陸這邊還沒有中文版,所以是一邊讀一邊翻譯,但是自己的英語水平很一般,所以並沒有以翻譯的角度來寫文章,怕自己的水平糟蹋了這本好書,所以基本上就是讀懂了書中的意思,然後按照自己的理解寫出來,截止 2017 年 6 月 5 號已經全部完成,目前是第一版,自己還在不斷調整語句、格式和內容。力求不誤導人。也希望廣大的 C++愛好者可以給我提出一些修改建議。

Effective Modern C++ 目錄

Tips:
1. 在模版型別推到的時候,如果傳遞的引數是引用型別,那麼可以看作是非引用型別的,也就是說型別的引用部分被忽略。
2. 當對通用引用型別的引數進行型別推導時,左值引數需要特殊對待。
3. 當推導正常的引數型別時,const和volatile型別的引數會被忽略掉const和volatile部分。
4. 在模版型別推導時,如果引數是陣列或是函式名會退化為指標,除非這些引數是用來初始化 引用的。

Tips:
1. auto型別推導通常和模版型別推導是一致的,但是auto型別推導對於{}會推導為std::initializer_list,但是模版型別無法對其進行推導
2. auto對於函式返回值的型別推導和lambda引數型別推導時就是隱式的模版型別推導,並不是auto型別推導,對於{}無法進行推導

Tips:
1. decltype推匯出來的型別幾乎總是和目標型別是一致的
2. 對於型別為T的左值表示式來說,decltype總是推導成T&
3. C++14支援decltype(auto)宣告變數和auto類似,可以對初始值進行型別推導,但是遵循的是decltype的規則

Tips:
1. 型別推導可以藉助於IDE,或者是編譯器的錯誤輸出以及Boost TypeIndex庫
2. 一些工具的型別推導結果可能是對我們所有幫助的,但是不一定是準確的,因此理解C++的型別推導規則仍然是必不可少的

Tips
1. auto變數必須初始化,可以緩解因為型別不匹配導致的可移植性或效率問題,可以緩解在重構過程中需要顯示輸入很長的變數型別名稱。
2. auto型別的變數受制於Item2和Item6中提到的陷阱

Tips
1. 對於一些看不見的proxy型別,使用auto對這類初始化表示式進行型別推導會推匯出錯誤的型別
2. 通過顯示的型別初始化慣用法可以強制auto推匯出目標型別

Tips
1. {}初始化是最廣泛的初始化語法,它可以阻止窄化轉換,並且避免了C++最複雜的語法解析
2. 在建構函式做函式過載的時候,{}會優先匹配帶有std::initializer_list引數的版本,即使其他建構函式看起來更匹配
3. 對與std::vector兩個引數的建構函式來說,其{}和()兩種初始化方式有很大的不同
4. 在模版中對於{}和()初始化如何進行選擇是一個挑戰

Tips
1. 優先使用nullptr替換0和NULL
2. 避免同時過載帶有整型引數和指標型別的引數

Tips
1. typedef 不支援模版化,但是using的別名宣告可以
2. 模版別名避免了傳統的typedef帶來的::type字尾,以及在型別引用的時候需要的typename字首
3. C++14給所有的C++11模版型別萃取提供了別名

Tips
1. C++98種的列舉眾所周知是無作用域限制的
2. C++11中的列舉類是有作用域限制的,不能進行隱式的型別轉換需要使用C++的型別cast進行轉換
3. 無論是列舉類還是傳統的列舉型別都支援指定底層的儲存,對於列舉類來說預設的底層儲存型別是int,而傳統的列舉型別其底層儲存是未知的,需要在編譯器進行選擇
4. 列舉類總是可以進行前向宣告的,而列舉型別則不行,必須是在明確指定其底層儲存的時候才能進行前向宣告

Tips
1. 優先使用delete來刪除函式替換放在私有作用域中未定義的
2. 任何函式都可以被刪除,包括非成員函式,模版例項化等

Tips
1. 對於要重寫的函式新增override關鍵字,讓編譯器負責檢查
2. 成員函式的引用識別符號可以識別出(*this)的不同,是左值型別,還是右值型別

Tips
1. 優先使用const_iterator替換iterator
2. 為了是程式碼更通用,應該優先使用非成員函式版本的begin、end、cbegin、cend等

Tips
1. noexcept 是函式介面的一部分,這意味著呼叫者會依賴它
2. 使用noexcept宣告的函式相比於沒有使用noexcept宣告的函式程式碼更具可優化性
3. noexpect對於move、swap、記憶體分配函式、解構函式等具有特別的價值
4. 大多數函式都是異常中立的而不是noexcept

Tips
1. constexpr物件是const,它的初始值是編譯期的
2. constexpr函式當傳入的引數是編譯期值時可以產生編譯期的結果
3. constexpr物件和函式可以廣泛使用在非constexpr修飾的物件和函式上下文中
4. constexpr關鍵字是物件以及函式介面的一部分

Tips
1. 為了讓const成員函式是執行緒安全的,除非你確定不會在併發環境中呼叫
2. 使用std::atomic變數可能會提供比mutex更好的效能,但是它僅僅適合操作單個變數和記憶體位置的操作

Tips
1. 編譯器可能會生成的特殊成員函式會有預設建構函式、解構函式、拷貝操作、移動操作等
2. 僅僅當類沒有顯式的宣告移動操作、拷貝操作和解構函式的時候,編譯器才會生成預設的移動建構函式
3. 僅僅當類沒有顯式的宣告拷貝建構函式、或是聲明瞭移動建構函式時編譯器才會生成預設的拷貝建構函式,和拷貝建構函式類似,僅僅當類沒有顯式的宣告拷貝賦值操作符或是移動構造操作符時編譯器才會生成預設的拷貝賦值操作符,不建議在具有明確宣告的解構函式的類中生成複製操作。
4. 成員函式模版不會阻止編譯器生成特殊的成員函式

Tips
1. std::unique_ptr很小、很快、是一個只能移動的,獨佔管理資源的智慧指標。
2. 預設情況看下資源的刪除使用的是delete,但是可以定製刪除器,有狀態的刪除器會導致std::unique_ptr物件的大小增長。
3. 將std::unique_ptr轉換為std::shared_ptr是很容易的

Tips
1. std::shared_ptr提供了方便的方法進行垃圾回收可以對任意共享資源的生命週期進行管理。
2. 相比於std::unique_ptr,std::shared_ptr物件通常情況下要大兩倍,這是因為它要分配一個控制塊,該控制塊中包含了一個原子型別的引用計數、刪除器、弱引用計數等
3. 預設的資源釋放是通過delete,但是std::shared_ptr支援自定義刪除器,並且刪除器的型別不影響std::share_ptr的型別和大小。
4. 避免從一個指標型別的變數建立std::shared_ptr

Tips
1. std::weak_ptr可以探查std::shared_ptr指向的指標是否是懸掛指標
2. 使用std::weak_ptr的一些潛在的場景包括,物件快取、觀察者模式、阻止std::shared_ptr的迴圈引用

Tips
1. 相比於直接使用new,make系列的函式消除了原始碼重複、提升了異常安全性,並且std::make_shared和std::allocate_shared生成的程式碼更小更快
2. 對於希望自定義刪除器以及通過{}進行初始值的設定時,不適合使用make系列函式
3. 對於std::shared_ptr來說有兩類場景不適合使用make系列函式,第一個就是需要自定義管理記憶體的,第二個就是管理大物件時,並且存在std::weak_ptr比std::shared_ptr生命週期更長的情況

Tips
1. Pimpl的慣用法通過減少類的使用者對類實現的編譯依賴從而縮小編譯所花費的時間
2. pImpl使用std::unique_ptr時,類的特殊函式宣告放在標頭檔案中,具體實現放在實現檔案中的,避免了標頭檔案中包含了std::unique_ptr析構相關的函式,導致編譯錯誤
3. 上面的建議適用於std::unique_ptr,但是不適用於std::shared_ptr

Tips
1. std::move的實現其實是一個無條件將型別cast轉換為右值型別,就本身而言沒有移動任何東西
2. std::forward就是根據引數的型別有條件的進行cast轉換,如果輸入的引數是一個右值就轉換為右值,否則就轉換為左值
3. 無論是std::move還是std::forward在執行時都沒有做任何事情

Tips
1. 如果一個模版函式的引數型別是T&&,並且T的型別是推匯出來的,或者一個物件使用auto&&這樣的宣告,那麼這個函式的引數和要宣告的物件都是通用引用型別
2. 如果型別的宣告不是type&&,或者沒有發生型別推導,那麼type&&表示的是一個右值引用
3. 如果通用引用初始化的時候傳入的是一個右值,那麼就和右值引用一樣,如果傳入的是左值那麼就喝左值引用是一樣的

Tips
1. 應用std::move到右值引用,std::forward到通用引用
2. 對於右值引用和通用引用作為值從函式返回時本質上都是做了相同的事情
3. 如果希望通過編譯器進行返回值優化,則不要將std::move或std::forward應用到區域性物件

Tips
1. 在通用引用上進行過載,幾乎總是會導致通用引用過載的版本比預期被呼叫的次數更頻繁。
2. 完美轉發建構函式是非常有問題的,因為它們通常比非const左值的拷貝建構函式更好地匹配,並且可以劫持派生類對基類拷貝構造和移動建構函式的呼叫。