[譯]C++17, 語言核心層有哪些新的變化?
看到一個介紹 C++17 的系列博文(原文),有十來篇的樣子,覺得挺好,看看有時間能不能都簡單翻譯一下,這是第一篇~
C++11, C++14, 以及 C++17. 我猜你已經看出了其中的命名模式: 今年(2017)的晚些時候,我們便會迎來新的C++標準(C++17). 今年的3月份, C++17已經達到了標準草案階段. 在我深入新標準的細節之前, 讓我們先來總體瀏覽一下C++17.(譯註:作者的文章寫於2017年初,當時C++17標準仍未正式釋出)
讓我們首先來看下C++標準整體的(特性)時間線.
The big picture
從 C++98 到 C++14,圖中只列出了較大的特性要點.圖中也缺少了關於 C++03 的特性描述, 因為C++03標準非常小,內容上更多是為了修復 C++98 的一些缺陷.如果你熟悉C++,那麼你一定知道 C++98(第一個C++標準) 和 C++11 是兩個非常大的C++標準, 但C++14,特別是C++03則是兩個小標準.
那麼 C++17 是大標準還是小標準呢?從我的觀點來看,答案其實挺簡單的: C++17 介於 C++14 和 C++11 之間,既不屬於大標準也不屬於小標準,至於原因,看看下面的說明吧.
概覽
C++17 在語言核心層和標準庫方面都有很多新改動.我們首先來看下語言核心層.
語言核心層
fold expressions(摺疊表示式)
C++11 開始支援可變引數模板(即支援任意多數量引數的模板).其中任意數量的模板引數儲存在引數包(parameter pack)中.在C++17中,你可以使用二元運算子直接化簡(reduce)引數包:
(譯註:譯文對作者的原始示例程式碼做了些許調整,原始程式碼請參看
#include <iostream> template<typename... Args> bool all(Args... args) { return (... && args); } int main() { std::cout << std::boolalpha; std::cout << "all(): " << all() << std::endl; std::cout << "all(true): " << all(true) << std::endl; std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl; std::cout << std::endl; return 0; }
上述程式碼中(第6行)使用的二元運算子是邏輯與(&&).程式的輸出如下:
對於摺疊表示式我想說的就是這些,如果你想了解更多的細節,可以看看我之前的一篇關於摺疊表示式的文章.
我們繼續來看看編譯期的改動
constexpr if
constexpr if 可以實現原始碼的條件編譯.
template <typename T>
auto get_value(T t)
{
if constexpr (std::is_pointer_v<T>)
return *t; // deduces return type to int for T = int*
else
return t; // deduces return type to int for T = int
}
如果 T 是指標型別,那麼上述程式碼中的第5行分支就會被編譯,反之則編譯第7行的程式碼分支.這裡有兩個要點: 函式 get_value 有兩種不同的返回型別並且 if 語句的兩個分支都必須有效.
在C++17中, for 語句的語法同樣適用於 if 和 switch 語句了.
initializers in if and switch statements
現在你可以直接在 if 和 switch 語句中初始化變量了.
std::map<int, std::string> myMap;
if (auto result = myMap.insert(value); result.second)
{
useResult(result.first);
// ...
}
else
{
// ...
} // result is automatically destroyed
初始化的變數僅在對應的 if 和 else 語句的作用域內有效,不會影響到外層作用域.
如果我們再結合使用一下C++17中新引入的結構化繫結宣告(structured binding declaration),那麼語法會更加優雅.
structured binding declaration(結構化繫結宣告)
藉助結構化繫結,我們可以直接將 std::tuple 或者某個結構的元素繫結到變數上去,讓我們用結構化繫結宣告來改寫一下之前的示例程式碼:
std::map<int, std::string> myMap;
if (auto[iter, succeeded] = myMap.insert(value); succeeded)
{
useIter(iter);
// ...
}
else
{
// ...
} iter and succeded are automatically be destroyed
第3行的 auto [iter, succeeded] 自動建立了兩個變數(iter 和 succeeded),他們會在第 11 行程式碼執行中(離開if的作用域)被銷燬.
結構化繫結宣告可以簡化程式碼,建構函式的模板引數推導同樣也可以.
Template deduction of constructors(建構函式的模板引數推導)
一個函式模板可以通過傳遞的函式引數進行引數的型別推導,但這條規則對於一個特殊的函式模板卻不適用:類模板的建構函式.在 C++17 中,類模板的建構函式也能進行引數的型別推導了:
#include <iostream>
template <typename T>
void showMe(const T& t)
{
std::cout << t << std::endl;
}
template <typename T>
struct ShowMe
{
ShowMe(const T& t)
{
std::cout << t << std::endl;
}
};
int main()
{
std::cout << std::endl;
showMe(5.5); // no need showMe<double>(5.5);
showMe(5); // no need showMe<int>(5);
ShowMe(5.5); // with C++17: no need ShowMe<double>(5.5);
ShowMe(5); // with C++17: no need ShowMe<int>(5);
std::cout << std::endl;
return 0;
}
22行和23行程式碼從C++第一個標準開始(C++98)便是合法的,但是25行及26行程式碼則只能在C++17中編譯通過,因為在C++17之前,你必須使用尖括號(<>)來指定需要例項化的類模板的型別引數.
除了功能特性,C++17中還有一些旨在提升程式碼執行效率的特性.
guaranteed copy elision
RVO是返回值優化(Return Value Optimisation)的簡稱,他的作用是允許編譯器移除一些不必要的複製操作,但RVO一直都只是一種可能優化步驟(並沒有標準規範,編譯器可以選擇進行RVO或者不進行RVO),C++17中通過定義 guaranteed copy elision 保證了這種優化的執行.
MyType func()
{
return MyType{}; // no copy with C++17
}
MyType myType = func(); // no copy with C++17
在這幾行程式碼的執行中可能會發生2次不必要的複製操作.第1次發生在第3行,第2次則發生在第6行.但在C++17中,這2次多餘的複製操作都(保證)不會發生.
如果返回值有名稱,我們便稱他為NRVO(Named Return Value Optimization,命名返回值優化):
MyType func()
{
MyType myVal;
return myVal; // one copy allowed
}
MyType myType = func(); // no copy with C++17
上述程式碼(第4行)與之前程式碼的一個細微差別是:在C++17中,編譯器仍然可以執行一次 myVal 的複製操作(也可以不執行復制),但第7行程式碼仍然保證不會發生複製操作.
如果你不再需要某個特性,甚至於某個特性可能會造成"危險",那麼你就應該移除他.C++17中就移除了auto_ptr 和 trigraphs 這兩個語言特性.
移除 auto_ptr 和 trigraphs
auto_ptr
std::auto_ptr 是C++標準中第一個智慧指標,他的設計目的是為了正確的管理資源.但是他存在一個很大的缺陷: std::auto_ptr 可以進行復制(和賦值)操作,但內部執行的卻是移動(move)操作!(譯註:意為 std::auto_ptr 複製(和賦值)操作會改變源運算元的內部資料,因此其不能進行邏輯獨立的複製(和賦值)操作,也是因為這個原因, std::auto_ptr 不能作為標準庫容器的元素).正因為 std::auto_ptr 的這個缺陷, C++11 中作為替代引入了不可複製(只可移動)的 std::unique_ptr.
std::auto_ptr<int> ap1(new int(2011));
std::auto_ptr<int> ap2= ap1; // OK (1)
std::unique_ptr<int> up1(new int(2011));
std::unique_ptr<int> up2= up1; // ERROR (2)
std::unique_ptr<int> up3= std::move(up1); // OK (3)
trigraphs(三字元組)
所謂三字元組(trigraphs),是指原始碼中由特定的3個字元組成的轉義字元序列(該轉義序列用以表達某個單字元),目的是解決一些鍵盤不能輸入某些特殊字元的問題.
C++ 中移除了三字元組(trigraphs),這意味著你不能使用C++17寫出下面這種"混亂"的程式碼了:
int main()
??<
??(??)??<??>();
??>
我猜你也許能看懂上面的程式碼,如果不能的話,你就必須把其中的三字元組(trigraphs)轉成對應的單字元了.
如果你對著上面的表格進行了轉換,你會發現上面的程式碼實際上就是定義了一個就地執行(just-in-place)的 lambda 函式.
int main()
{
[]{}();
}
(譯註:文章中的不少說明涉及到了程式碼行號,但譯文中的示例程式碼並沒有行號顯示,原因是自己未找到markdown中原始碼顯示行號的簡易方法,有知道的朋友可以告訴一聲)