您的位置:首頁>正文

ISO C++ 14 重點介紹

原文連結

http://marknelson.us/2014/09/11/highlights-of-iso-c14/

下面是對你的日常開發有重大影響的C++14新變動, 列出了一些示例代碼, 並討論何時以及為什麼要使用它們。

1. 返回數值型別推導(Return type deduction)

對auto做進一步的闡述是很有趣的事情。 C++仍然是類型安全的, 但是類型安全機制越來越多的由編譯器來執行, 而非程式師自己。 在C++11中, 程式師已經開始使用auto來進行聲明了。 當使用全限定類型名稱(fully qualified type name )會讓你感到吃驚時(因為太長了), 例如, 創建反覆運算器, 這時候你會感激auto的出現。 新發明的C++代碼更加易讀:

1 for ( auto ii = collection.begin ; ...

上面的代碼仍然是類型安全的——編譯器知道begin在其所在的上下文中會返回什麼類型,

因此關於類型ii是什麼就不再有問題了, 並且對每個使用auto的地方都會進行檢查。

在C++ 14中, auto的使用在幾個方面進行了擴展。 一個非常有意義的地方是返回類型推導(return type deduction)。 如果我在函數內部寫下這樣一行代碼:

1 return 1.4;

編譯器和我都清楚的知道函數的返回值是double。 所以在C++14中, 我可以將函數返回類型定義為auto而不是double:

1 auto getvalue {

這個新特性的細節非常容易被理解。 舉個例子, 如果一個函數有多個返回值, 它們需要有相同的類型。 代碼如下:

1 auto f(int i) 2 { 3 if ( i

看上去應該將返回類型推導為double, 但是標準會禁止這種模棱兩可的行為, 編譯器會發出抱怨:

error_01.cpp:6:5: error: 'auto' in return type deduced as 'double' here but deduced as 'int' in

earlier return statement

return 2.0

^

1 error generated.

為什麼返回類型推導對於C++程式來說是錦上添花的。 首先, 有時候你必須返回一個非常複雜的類型, 比如在對標準庫容器進行搜索的時候返回一個反覆運算器。

auto返回類型使得函數更加易讀, 易寫。 其次, 這個原因可能不是那麼明顯, 使用auto返回類型能夠增強你的重構能力。 舉個例子, 考慮下面的代碼:

1 #include 2 #include 3 #include 4 5 struct record { 6 std::string name; 7 int id; 8 }; 9 10 auto find_id(const std::vector &people, 11 const std::string &name) 12 { 13 auto match_name = [&name](const record& r) -> bool { 14 return r.name == name; 15 }; 16 auto ii = find_if(people.begin, people.end, match_name ); 17 if (ii == people.end) 18 return -1; 19 else 20 return ii->id; 21 } 22 23 int main 24 { 25 std::vector roster = { {"mark",1}, 26 {"bill",2}, 27 {"ted",3}}; 28 std::cout

在這個例子中, 使find_id返回auto比返回int並沒有使我節省多少腦力。 但是考慮一下, 如果我決定重構record結構體, 會發生生麼。 這次我不在record物件中使用一個整型來唯一標記一個人了, 而是使用一個新的GUID類型:

1 struct record { 2 std::string name; 3 GUID id; 4 };

對record物件所做的這個改變會導致一系列的連鎖反應, 比如函數的返回類型會發生變化。 但是如果我對函數返回值使用自動類型推導, 編譯器會默默的應對這些變化。 任何在大型工程上進行開發的C++程式師都會熟悉這個問題。 對單一資料結構的修改會導致無休止的對原代碼的反覆運算,

對變數, 參數以及返回類型的修改。 auto的廣泛使用能夠消減很大一部分這樣的工作。

注意:在上面的例子和剩下的章節中, 我會創建和使用命名的lambda.我猜測大多數用戶在使用類似std::find_if的lambdas函數時, 都會將其定義為匿名inline物件, 這是非常便利的使用方式。 鑒於頁面寬度有限, 我認為命名lambda在你的流覽器中更加易讀。

所以這種寫法你不必特地的模仿, 僅僅是更加易讀而已。 特別在你沒有lambda經驗的情況下, 讀起來會簡單些。

使用auto作為返回值的立竿見影的效果是reality of it's doppelganger, decltype(auto), 還有有了類型推導的規則。 現在你可以使用它來自動獲取類型資訊了, 如下面的代碼片段:

1 template 2 struct finder { 3 static decltype(Container::find) finder1 = Container::find; 4 static decltype(auto) finder2 = Container::find; 5 };

2. 泛型lambdas

另外一個auto悄悄潛伏的地方是在lambda參數的定義中。

使用auto型別宣告定義lambda參數同創建範本函數基本相當。 lambda會基於參數推導類型來進行特定的產生實體。

這對創建可在不同上下文中重用的lambdas來說是很方便的。 在下面的簡單例子中, 我創建了一個lambdas用做標準庫函數的謂詞(predicate)。 在C++11的世界裡, 我分別為整型加法, 字串加法顯示的產生實體了一個lambda。

有了泛型lambda, 我能用其定義單一的lambda。 雖然它的語法不包括關鍵字template, 這仍然是對C++泛型程式設計的進一步擴展:

1 #include 2 #include 3 #include 4 #include 5 6 int main 7 { 8 std::vector ivec = { 1, 2, 3, 4}; 9 std::vector svec = { "red", 10 "green", 11 "blue" }; 12 auto adder = (auto op1, auto op2){ return op1 + op2; }; 13 std::cout

產生如下結果:

int result : 10

string result : redgreenblue

即使你對匿名inline lambdas進行產生實體, 正如如前面討論的, 泛型參數仍然是有用的。 當你的資料結構發生了變化或者APIs中的函數被修改了, 對泛型lambdas進行調整時只需要重新編譯就可以了, 不需要重新實現:

1 std::cout

3. 被初始化的lambdas捕獲(Initialized lambda captures)

在C++11中我們必須開始適應lambda捕獲特化(lambda capture specification)的概念。

這種聲明會在創建閉包(closure)的時候對編譯器進行引導:lambda定義了一個函數的實例, 還有定義在lambda作用域之外的綁定在函數上的變數。

在早期的推導返回類型的例子中, 我實現了捕獲單個變數名字的一個lambda定義, 用做謂詞中搜索字串的源:

1 auto match_name = [&name](const record& r) -> bool { 2 return r.name == name; 3 }; 4 auto ii = find_if(people.begin, people.end, match_name );

這種特殊的捕獲使lambda獲取了按引用訪問變數的許可權。 捕獲也能按值來執行, 在兩種情況中, 變數的使用方式都會符合C++的直覺。 按值捕獲意味著lambda在本地變數的拷貝上進行操作, 按引用捕獲意味著lambda在週邊作用域的變數本身進行操作。

這些都很好, 但也會有伴隨而來的一些局限性。 我想委員會感覺需要處理的一件事情是使用move-only語義來初始化捕獲變數。

這意味著什麼?如果我們認為lambda即將成為參數的接收器,我們想使用move語義捕獲外部變數。舉個例子,考慮如何使lambda接收一個move-only unique_ptr參數。第一個嘗試是按值捕獲,失敗了:

1 std::unique_ptr p(new int); 2 *p = 11; 3 auto y = [p] { std::cout

這會生成一個編譯錯誤因為unique_ptr沒有拷貝構造函數——它所想的就是禁止拷貝。

將其改為按引用捕獲就能編譯通過,但是沒有達到預期效果:也就是通過將值move到本地的拷貝來接收參數。最後你可以通過先創建本地變數然後在捕獲的引用上調用std::move來完成,但是效率不高。

通過對捕獲語句語法進行修改可以修復這個問題。現在我們不是只聲明一個捕獲變數,我們也能對其初始化。看下面的例子:

1 auto y = [&r = x, x = x+1]->int {...}

上面的代碼捕獲了x的拷貝,同時為x增加了1。這個例子很容易理解,但是我不確定它是否為接收move-only變數捕獲了這種新語法的值。使用這個新語法的例子如下:

1 #include 2 #include 3 4 int main 5 { 6 std::unique_ptr p(new int); 7 int x = 5; 8 *p = 11; 9 auto y = [p=std::move(p)] { std::cout

在這個例子中,捕獲的值p用move語義來初始化,在沒有聲明本地變數的情況下有效的接收了指標:

inside: 11

Segmentation fault (core dumped)

這個令人討厭的結果正是你想要的——在p被捕獲並且move到lambda中後,代碼對p進行瞭解引用。

4. [[棄用的]][[deprecated]]屬性

初次看到deprecated 屬性是在java中,我承認有點嫉妒。對大多數程式師來說代碼腐爛(rot)是一個巨大的問題。(曾經鼓勵刪除代碼?但我從來沒有這麼做過)。這個新屬性提供了攻克這個問題的系統級別的方法。

用起來很簡單,將標籤【[[deprecated]]放在聲明之前就可以了,聲明可以為類,變數,函數或其他東西。結果像下面這個樣子:

1 class 2 [[deprecated]] flaky { 3 };

當你的程式使用了一個deprecated實體,原本需要編譯器做出反應,現在留給了代碼實現者。很清楚大多數人希望能夠看到某種警告,也能隨手把這種warning關掉。舉個例子,clang3.4在產生實體一個deprecated類的時候會發出以下警告:

dep.cpp:14:3: warning: 'flaky' is deprecated [-Wdeprecated-declarations]

flaky f;

^

dep.cpp:3:1: note: 'flaky' declared here

flaky {

^

C++的屬性標記語法看上去有點不熟悉。在屬性清單中,[[deprecated]]被放在關鍵字(如class 或者enum)之後,實體名字之前。這個標記有另外一種形式,它包含了一個資訊參數。由開發人員決定如何寫這個資訊。clang3.4忽略了這個資訊。

看下面的代碼片段

1 class 2 [[deprecated]] flaky { 3 }; 4 5 [[deprecated("Consider using something other than cranky")]] 6 int cranky 7 { 8 return 0; 9 } 10 11 int main 12 { 13 flaky f; 14 return cranky; 15 }

它並沒有包含error資訊:

dep.cpp:14:10: warning: 'cranky' is deprecated [-Wdeprecated-declarations]

return cranky;

^

dep.cpp:6:5: note: 'cranky' declared here

int cranky

^

5. 二進位數字字和數字分隔符號

有兩個新功能不是驚天動地的,但他們確實代表了很好的句法結構的改善。這樣的很小的改變改善了代碼可讀性,進一步減少了bug數量。C++ 程式師現在可以創建一個二進位數字字,向已經包含十進位,十六進位以及很少使用的八進制的標準中又添加了一員。二進位數字字使用首碼0b後面緊接數位。在美國和英國,我們使用逗號來作為數字分隔符號,如:$1,000,000。這種寫法真正方便了讀者,使得我們的大腦處理很長的數位時更加容易。因為同樣的原因C++標準委員會添加了數字分隔符號。它們不影響數值,只是通過分塊讓數位的讀寫更加容易。

數位分隔符號使用什麼字元?在C++中基本上每個標點符號都被特定的特性使用了,因此沒有很明顯的選擇。最後的選擇是使用單引號字元,使得C++的百萬數表示如下:1'000'000.00。記住分隔符號對數值沒有任何影響,因此百萬數也可表示如下:1'0'00'0'00.00。下面的例子使用了兩個新特性:

1 #include 2 3 int main 4 { 5 int val = 0b11110000; 6 std::cout

結果也是你所意料的:

Output mask: 33152

Proposed salary: $300000

6. 剩餘特性

c++的其他新特性無需多述。變數範本在變數上對範本的擴展。總會使用到的例子是變數pi的一個實現。當實現為double的時候,變數會返回3.14,當實現為int時,它可能返回3,當實現為string時,可能返回“3.14”或者"pi",這是個很棒的特性,以前是在中實現的。變數範本的語法和語義和類範本是基本相同的——你無需額外的學習就能使用它們。對constexpr函數的限制放鬆了,例如,可以有多個返回值,可以在內部使用case和if語句,可以用迴圈以及其它。這就對能在編譯器做的事進行了擴展,為範本的引入插上了翅膀。其他小的特性包括為記憶體分配指定大小(sized deallocations)和一些語法的整理(tidying)。

7. 下一步做啥?

通過對語言進行改善來保持語言的流行,C++委員會感覺到了壓力,它們已經為另外一個標準進行準備了,也就是C++17。

可能更加有趣的事情是創立一些小組,這些小組可以創建技術規格和文檔,雖然不會達到標準的水準,但是會被ISO委員會發佈和支持。大概這會更加快速的執行。委員會當前致力於8個部分,包括:

檔案系統併發(Concurrency)並行(Parallelism)網路概念(Concepts )

這些技術規格的成功與否必須由是否被採納和使用來評判。如果我們發現所有實現都使用了這些規格,那麼為這個規格建立的新軌道就算成功了。

C/C++一致保持著很好的使用度。現代C++,我們從C++11開始算起,在語言易用性和沒有損害性能前提下的安全性有了長足的進步。對於特定類型的工作,很難有理由來將C或者C++替換掉。C++14標準並不像C++11跳躍度這樣大,但是它使語言在一個很好的方向上。如果標準委員會在接下來的10年能夠在生產率上維持當前的水準,在以性能為導向的應用中C++應該會繼續成為被選擇的語言。

這意味著什麼?如果我們認為lambda即將成為參數的接收器,我們想使用move語義捕獲外部變數。舉個例子,考慮如何使lambda接收一個move-only unique_ptr參數。第一個嘗試是按值捕獲,失敗了:

1 std::unique_ptr p(new int); 2 *p = 11; 3 auto y = [p] { std::cout

這會生成一個編譯錯誤因為unique_ptr沒有拷貝構造函數——它所想的就是禁止拷貝。

將其改為按引用捕獲就能編譯通過,但是沒有達到預期效果:也就是通過將值move到本地的拷貝來接收參數。最後你可以通過先創建本地變數然後在捕獲的引用上調用std::move來完成,但是效率不高。

通過對捕獲語句語法進行修改可以修復這個問題。現在我們不是只聲明一個捕獲變數,我們也能對其初始化。看下面的例子:

1 auto y = [&r = x, x = x+1]->int {...}

上面的代碼捕獲了x的拷貝,同時為x增加了1。這個例子很容易理解,但是我不確定它是否為接收move-only變數捕獲了這種新語法的值。使用這個新語法的例子如下:

1 #include 2 #include 3 4 int main 5 { 6 std::unique_ptr p(new int); 7 int x = 5; 8 *p = 11; 9 auto y = [p=std::move(p)] { std::cout

在這個例子中,捕獲的值p用move語義來初始化,在沒有聲明本地變數的情況下有效的接收了指標:

inside: 11

Segmentation fault (core dumped)

這個令人討厭的結果正是你想要的——在p被捕獲並且move到lambda中後,代碼對p進行瞭解引用。

4. [[棄用的]][[deprecated]]屬性

初次看到deprecated 屬性是在java中,我承認有點嫉妒。對大多數程式師來說代碼腐爛(rot)是一個巨大的問題。(曾經鼓勵刪除代碼?但我從來沒有這麼做過)。這個新屬性提供了攻克這個問題的系統級別的方法。

用起來很簡單,將標籤【[[deprecated]]放在聲明之前就可以了,聲明可以為類,變數,函數或其他東西。結果像下面這個樣子:

1 class 2 [[deprecated]] flaky { 3 };

當你的程式使用了一個deprecated實體,原本需要編譯器做出反應,現在留給了代碼實現者。很清楚大多數人希望能夠看到某種警告,也能隨手把這種warning關掉。舉個例子,clang3.4在產生實體一個deprecated類的時候會發出以下警告:

dep.cpp:14:3: warning: 'flaky' is deprecated [-Wdeprecated-declarations]

flaky f;

^

dep.cpp:3:1: note: 'flaky' declared here

flaky {

^

C++的屬性標記語法看上去有點不熟悉。在屬性清單中,[[deprecated]]被放在關鍵字(如class 或者enum)之後,實體名字之前。這個標記有另外一種形式,它包含了一個資訊參數。由開發人員決定如何寫這個資訊。clang3.4忽略了這個資訊。

看下面的代碼片段

1 class 2 [[deprecated]] flaky { 3 }; 4 5 [[deprecated("Consider using something other than cranky")]] 6 int cranky 7 { 8 return 0; 9 } 10 11 int main 12 { 13 flaky f; 14 return cranky; 15 }

它並沒有包含error資訊:

dep.cpp:14:10: warning: 'cranky' is deprecated [-Wdeprecated-declarations]

return cranky;

^

dep.cpp:6:5: note: 'cranky' declared here

int cranky

^

5. 二進位數字字和數字分隔符號

有兩個新功能不是驚天動地的,但他們確實代表了很好的句法結構的改善。這樣的很小的改變改善了代碼可讀性,進一步減少了bug數量。C++ 程式師現在可以創建一個二進位數字字,向已經包含十進位,十六進位以及很少使用的八進制的標準中又添加了一員。二進位數字字使用首碼0b後面緊接數位。在美國和英國,我們使用逗號來作為數字分隔符號,如:$1,000,000。這種寫法真正方便了讀者,使得我們的大腦處理很長的數位時更加容易。因為同樣的原因C++標準委員會添加了數字分隔符號。它們不影響數值,只是通過分塊讓數位的讀寫更加容易。

數位分隔符號使用什麼字元?在C++中基本上每個標點符號都被特定的特性使用了,因此沒有很明顯的選擇。最後的選擇是使用單引號字元,使得C++的百萬數表示如下:1'000'000.00。記住分隔符號對數值沒有任何影響,因此百萬數也可表示如下:1'0'00'0'00.00。下面的例子使用了兩個新特性:

1 #include 2 3 int main 4 { 5 int val = 0b11110000; 6 std::cout

結果也是你所意料的:

Output mask: 33152

Proposed salary: $300000

6. 剩餘特性

c++的其他新特性無需多述。變數範本在變數上對範本的擴展。總會使用到的例子是變數pi的一個實現。當實現為double的時候,變數會返回3.14,當實現為int時,它可能返回3,當實現為string時,可能返回“3.14”或者"pi",這是個很棒的特性,以前是在中實現的。變數範本的語法和語義和類範本是基本相同的——你無需額外的學習就能使用它們。對constexpr函數的限制放鬆了,例如,可以有多個返回值,可以在內部使用case和if語句,可以用迴圈以及其它。這就對能在編譯器做的事進行了擴展,為範本的引入插上了翅膀。其他小的特性包括為記憶體分配指定大小(sized deallocations)和一些語法的整理(tidying)。

7. 下一步做啥?

通過對語言進行改善來保持語言的流行,C++委員會感覺到了壓力,它們已經為另外一個標準進行準備了,也就是C++17。

可能更加有趣的事情是創立一些小組,這些小組可以創建技術規格和文檔,雖然不會達到標準的水準,但是會被ISO委員會發佈和支持。大概這會更加快速的執行。委員會當前致力於8個部分,包括:

檔案系統併發(Concurrency)並行(Parallelism)網路概念(Concepts )

這些技術規格的成功與否必須由是否被採納和使用來評判。如果我們發現所有實現都使用了這些規格,那麼為這個規格建立的新軌道就算成功了。

C/C++一致保持著很好的使用度。現代C++,我們從C++11開始算起,在語言易用性和沒有損害性能前提下的安全性有了長足的進步。對於特定類型的工作,很難有理由來將C或者C++替換掉。C++14標準並不像C++11跳躍度這樣大,但是它使語言在一個很好的方向上。如果標準委員會在接下來的10年能夠在生產率上維持當前的水準,在以性能為導向的應用中C++應該會繼續成為被選擇的語言。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示