深入理解 C++11 讀書筆記


聲明: 所有圖片均為我在騰訊博客的原創, 但是從我的騰訊微博轉過來就被流氓的打了標簽. 因此不涉及版權問題, 可以隨意使用.


 

C++11 中棄用了 `throw(type1, type2...)` 這種異常聲明方式. 但是庫中大量使用了 noexcept 代替原有異常機制, 因此提高了庫的效率.

默認析構函數是 noexcept(true) 的, 但是如果基類析構函數是 noexcept(false), 那么自雷析構函數默認也是 noexcept(false) 的.

 

類中成員變量的初始化順序: 1, 就地初始化. 2, 初始化列表. 3, 構造函數體. (目前, 最新的 vs2013 preview 還不持支此特性)

注: 此版本 vs 尚不支持就地初始化, 所以此代碼僅供參考.

委托構造, 即一個構造函數依賴於另一個構造函數.

 

 

曾經, C++ 是無法直接獲取類的成員變量的大小的, 通常我們會 `sizeof(reinterpret_cast<X *>(0)->m_foo)`. 而 11 終結了這個蹩腳的語法, 對於普通對象成員使用 `sizeof(X::m_foo)` 即可.

 

類成員函數后面的 override 和 final 真是一對好朋友. 在編寫多層次繼承時可以極大避免程序員犯錯誤. 我們需要這樣的編譯器! 當然, 如果你非寫成這樣: `virtual foo() final = 0;` 的成員函數, 也是可以的.

關於 `virtual void foo() final = 0;`, 如果面試的時候考官問你: 怎么能讓一個類不能實例化? 除了回答 private dctor 以外, 還可以使用這條技巧.

 

return rvalue 在表達式結束后, 其生命周期也就結束了, 而通過右值引用的匿名變量, 該右值生命周期將會與右值引用類型的變量的生命周期一樣. 只要這個引用變量還 "活着", 該右值臨時量將會一直 "存活" 下去.

在 98 中, const & 類型也可以將臨時變量的聲明期延長至引用變量周期. 原來如此...

 

'std::move 基本等同於一個類型轉換: static_cast<T &&>(lvalue);' 這句話驗證了我一年前剛學 move semantic 的疑惑. 我當時就認為, 既然 lvalue 可以通過變為匿名的手段成為 rvalue, 那么 static_cast 就可以勝任. 結果今天終於找到證實了.

std::move 和 std::forward 在實際實現的差別並不大. 不過標准庫這么設計, 也是為了讓每個名字對應不同的用戶, 以應對未來可能的擴展.

 

標准庫中, std::move_if_noexcept 如果參數有 noexcept 移動構造函數, 則返回右值引用; 否則返回左值引用. 這使得 `T tNew = std::move_if_noexcept(tOld);` 非常安全.

 

11 將 explicit 關鍵字擴展到類的類型轉換上, 對程序的健壯性起了積極作用. 類似的改動還有類成員函數的 override 和 final.

wiki: "在 C++11 中,關鍵字 explicit 修飾符也能套用到類型轉換子上。如同建構式一樣,它能避免類型轉換子被隱式轉換調用。但 C++11 特別針對布林值轉換提出規范,在 if 條件式,循環,邏輯運算等需要布林值的地方,編譯器能為符合規范的表示式調用用戶自定的布林類型轉換子。" (感謝 rui)

 

匿名聯合體的成員在 class 中能夠直接被引用. 這是 98 中無法做到的, 通常, 98 會給 union 起一個名字, 然后再通過這個名字引用其中的成員, 着實不直觀而且還麻煩. 起名字是程序員最大的痛苦.

經過測試, 在 vs2013 preview 中, 匿名 struct 和 class 也是被支持的.

 

用戶自定義字面量為程序員提供了非常直觀 & 方便的語法糖. 我又想起了那句話: 好的編程語言是讓程序員用更少的代碼完成更多的事情. 推薦閱讀: http://akrzemi1.wordpress.com/2013/01/04/preconditions-part-i/ 以及 http://www.cnblogs.com/walfud/articles/3280629.html

字面值為 char: 字面量操作符參數只能為 char.

字面量為 const char *: 操作符參數只接受 (const char *, size_t_).

字面值為浮點型: 字面量操作符只接受 long double 或者 const char * 作參數. 同理, 如果 long double 容納不下, 調用 const char * 版本處理函數.

字面量為整數: 操作符函數只能接受 unsigned long long 或者 const char * 作為參數. 如果 unsigned long long 容納不下, 則自動調用 const char * 操作符處理函數.

 11 中, 自定義字面量操作符需要注意: 1, `operator "" _XXX` 中, 雙引號和自定義后綴之間必須有一個空格. 2, 建議使用下划線開始的自定義后綴, 避免和 C/C++ 關鍵字沖突.

 

inline namespace 我看不出你有什么好. 命名就是破壞了 namespace 的隔離性. 所以 Do NOT use it unless you know what you are doing.

 

使用 using 定義別名的好處是可以支持 template. 比如 `template <typename T> using MapString = std::map<T, std::string>`. 這是 typedef 無法做到的.

 

auto 對於 cv-qualifier 的支持: auto 並不從初始化表達式中帶走 cv-qualifier. 但是對於指針和引用則會保持原有的 cv 類型. 見圖片.

在 11 中, 有幾種情況是 auto所不能的:

  1: 不能作為函數參數類型, 更不能在函數參數中自動推導.

  2: 不能作為結構體非靜態成員, 同樣也不能推導.

  3: 不能聲明 auto arr[n] 這樣的數組.

  4: 不能作為模板的參數.

 

decltype 與 auto 不同, decltype 能夠帶走 "cv" 限定符, 不過, decltype 不會繼承對象的 cv 屬性. 如圖:

decltype(v) 也有一些詭異的規則, 且看:

1, 如果 v 是一個不帶括號的標記符表達式(id-expr) 或類成員表達式, 則 decltype(v) 代表 v 的類型. 此外, 如果 v 是一個被重載的函數, 則導致編譯錯誤.

2, 否則, 假設 v 類型是 T, 如果 v 是一個將亡值(xvalue), 那么 decltype(v) 為 T &&.

3, 否則, 假設 v 類型 T, 如果 v 是一個左值, 則 decltype(v) 為 T &.

4, 否則, 假設 v 類型 T, 則 decltype(v) 為 T.

其中最蹩腳的就是規則 3. 看幾個例子: int i = 0; int *pI = &i; decltype(i) => int, decltype(*pI) => int &, decltype(i++) => int &, decltype(++i) => int. <深入理解 C++11> 這書講的不錯, 詳細內容請看 4.3.3

此外, decltype 能夠允許冗余的 cv 限定符. 比如 const int i = 0; const decltype(i) ci = 0; 也是允許的, 雖然 decltype(i) 已經是 const int 類型.

  

有人認為宏和常量都要全大寫, 這是對全大寫作用的誤讀. 在傳統的 C 語言中, 全大寫是因為宏處理發生在預編譯階段, 任何包含該該字母串的內容都會被替換, 因此會擾亂正常代碼. 而開發者為了避免這種情況, 才將宏全大寫. 枚舉雖然也是常量, 但是會擾亂正常代碼么? 顯然不會, 因此就無需全大寫.

 

11 終於把 enum 的超級作用域做了完善. 在 98 中, enum 作用域是會被自動導出到父作用域的, 導致不同的枚舉中也不能存在相同的枚舉值, 這是何等違背作用域的理念. 在 11 中, enum class 將枚舉限制在了枚舉類中, 增強了作用域的概念, 因此不同枚舉類中完全可以存在同名同值的枚舉值, 這讓代碼更加一致.

 

11 中加入了很多對並行編程的支持. 比如 atomic 模板, thread 等, 極大方便了並行程序設計. 讓程序員將精力集中在要解決的問題上, 而不是互斥訪問變量和如何創建線程這些問題上.

在 11 的並行支持中, 有個重要的概念叫做 "內存模型". 目前 11 支持 6 種內存模型. 這些模型分門別類的作為 atomic 對象的 load 和 store 第二參數使用. 其作用就是約束機器指令的執行順序以及添加內存欄柵.

memory_order_relaxed: 不對執行順序作任何保證.

memory_order_acquire: 本線程中, 所有后續的讀操作必須在本原子操作完成后執行.

memory_order_release: 本線程中, 所有之前的寫操作完成之后才能執行本原子操作.

memory_order_acq_rel: 同時包含 memory_order_acquire 和 memory_order_release 標記.

memroy_order_consume: 本線程中, 所有后續的有關本原子類型的操作, 必須在本條原子操作完成之后執行.

memory_order_seq_cst: 全部存取都按順序執行. 這也是 C++11 所有 atomic 原子操作的默認值.

 

constexpr 可以作為編譯期常量使用, 不過大部分功能還是可以使用傳統 enum 來實現. 但是, 使用 constexpr 進程 metal programming 是一個新趨勢, 如圖: 其中 f 是一個編譯期常量, 通過單步調試可以發現, Fabonacci 無法 step into.

 

關於可變數量模板, 這里有一份極好的解釋: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2087.pdf請搜索 printf 的實現. 11 把更多的工作留給了編譯器去做, 這會不會讓原本已經非常慢的編譯變得更加慢? 我心有余悸, 或是杞人憂天.

變長模板中也存在一些易錯點: 如模板解包的時候, 比如有 `typename ...Arg`, 那么 `class C<Arg...>` 和 `class C<Arg>...` 是不一樣的. 前者代表 `class C<X, Y>`, 后者代表 `class C<X>, class C<Y>`.

 

空指針一直是 C++ 的一個痛處. 因為 NULL 要么是 0, 那么是 (void *)0. 然而兩者都是用語法技巧來躲避編譯錯誤, 它們的問題如下圖. 對於空指針, 11 給出了更好的答案: nullptr.

nullptr 的類型是 nullptr_t, nullptr_t 的 ==, <=, >= 可以和任何 nullptr_t 以及指針類型做運算, 而且都返回 true. 但是在我的 vs2013preview 里測試, 發現有地址的指針總是大於 nullptr, 因此推斷 vs2013preview 的實現還是一個整數.

此外, 11 規定, nullptr 的大小和 void * 的大小保持一致.

 

`=default` 可以在類中產生默認的構造函數和 operator= 函數. 這有助於保持一個類是 POD 的. `=delete` 則顯示刪除某個函數, 即不能夠生成或使用該函數. 但是標准不建議將 `=delete` 與 'explicit' 共用, 這會產生一些混亂, 請參考 <深入理解 C++11> p.232

 `=delete` 除了可以作用在類中, 還可以作用在類外, 這種用法通常是禁止類型轉換帶來的副作用. 再次聲明, 不要將 `explicit` 與 `=delete` 合用. 因為兩者合用代表 '刪除顯示的構造', 也就是阻止顯示構造而允許隱士構造, 這往往不是我們想要的.

因為 `=delete` 可以作用在普通函數上, 所以我們可以對全局的 new 進行刪除, 這樣就阻止了某個對象在堆上創建. 這真是一個歪點子.

 

如果你沒使用過 lambda, 那就沒使用過 C++11! lambda 可以方便的將 "短小精悍" 的功能隨意的插入代碼之間. 我們曾經為了和 stl 配合遍歷一個稍復雜些的容器, 通常都寫一個 class 並重載 `operator()`, 這既不直觀, 書寫也不方便. lambda 你姍姍來遲啊...

lambda 的全貌: `[capture](paramters) mutable -> return-value {statement}`. 默認情況下, lambda 總是一個 const 函數, 不過 mutable 可以取消其常量性.

lambda 有一個重要的 "陷阱". 即: 通過 "值傳遞" 方式捕獲的變量, 在其聲明的時候值就已經定好了. 而 "引用傳遞" 方式捕獲的變量, 會隨着程序運行而修改.

只有具有函數作用域的變量, 才能被 lambda 捕獲, 全局變量是不行的. 這也避免了全局變量沒有初始化就被 lambda 使用值傳遞方式捕獲, 而產生錯誤的行為.

有的時候, lambda 會報和構造函數有關的錯誤, 這是因為 lambda 的底層實現可能就是一個匿名重載了 `operator()` 的 class, 也就是我們熟悉的仿函數.

`[](){}` 這就是一個合法的 lambda, 更有甚者, 會寫出這樣的 lambda `[]{}`.

`auto i = [](){return 1;}();` 注意最后的一對小括號, lambda 允許你定義的時候直接調用.

每個 lambda 的作用域都是其所在函數.

lambda 在 11 標准中默認都是內聯的.

lambda 美中不足, 沒有 const & 捕獲方式.

 


 

另:

關於本書的勘誤, 是我自己總結的, 可能不是很全, 希望能對你有幫助並期待你的補充: http://www.cnblogs.com/walfud/articles/2057799.html (搜索 "<深入理解 C++11: C++11 新特性解析與應用>").

我的一篇關於 C++11 最討人喜歡的幾個特性的博客: http://www.cnblogs.com/walfud/articles/3280629.html

我的另一篇關於 C++11 FAQ 的博客: http://www.cnblogs.com/walfud/articles/2846227.html

某日, 我看了一篇博客, 是說 c++11 中幾個違背直覺的地方, 雖說有少許錯誤, 但是還是值得思考的: http://url.cn/GYElGe

11 中引入了正則表達式, 這是一個卓越的特性, 但是本書沒有提及也沒有介紹. 請參考: http://www.cnblogs.com/walfud/articles/2846227.html 中搜索 "regular expression".


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM