C++14

2014版C ++編程語言標準

C++14C++的現行標準的非正式名稱,正式名稱為"International Standard ISO/IEC 14882:2014(E) Programming Language C++"。C++14旨在作為C++11的一個小擴充,主要提供漏洞修復和小的改進。C++14標準的委員會草案(Committee Draft)N3690於2013年5月15日發表。[1]工作草案(Working Draft)N3936已於2014年3月2日完成。最終的投票期結束於2014年8月15日,結果(一致通過)已於8月18日公佈。[2]

新的語言特性

以下為在C++14中被加入語言核心的特性。

泛型的lambda

在C++11中,lambda函數的形式參數需要被聲明為具體的類型。C++14放寬了這一要求,允許lambda函數的形式參數聲明中使用類型說明符auto[3]

auto lambda = [](auto x, auto y) {return x + y;}

泛型lambda函數遵循模板參數推導的規則。以上代碼的作用與下面的代碼相同:[4]

struct unnamed_lambda
{
  template<typename T, typename U>
    auto operator()(T x, U y) const {return x + y;}
};
auto lambda = unnamed_lambda{};

Lambda擷取部分中使用表達式

C++11的lambda函數通過值拷貝(by copy)或參照(by reference)擷取(capture)已在外層作用域聲明的變數。這意味着lambda擷取的變數不可以是move-only的類型。[5]

C++14允許lambda成員用任意的被擷取表達式初始化。這既允許了capture by value-move,也允許了任意聲明lambda的成員,而不需要外層作用域有一個具有相應名字的變數。[6]這稱為廣義擷取(Generalized capture)。[7]即在擷取子句(capture clause)中增加並初始化新的變數,該變數不需要在lambda表達式所處的閉包域(enclosing scope)中存在;即使在閉包域中存在也會被新變數覆蓋(override)。新變數類型由它的初始化表達式推導。用途是可以從外層作用域中擷取只供移動的變數並使用它。

也即,允許建立lambda擷取並用任意表達式初始化,擷取中的名字不需要一定出現在閉包域,初始化表達式在lambda被建立(而不是被呼叫)時求值。 使用參照擷取可以對被參照的外部閉包的變數使用別名。

auto x = 1;
auto f = [&r = x, x = x * 10] {
  ++r;
  return r + x;
};
f(); // sets x to 2 and returns 12

這是通過使用一個初始化表達式完成的:

auto lambda = [value = 1] {return value;}

lambda函數lambda的返回值是1,說明value被初始化為1。被聲明的擷取變數的類型會根據初始化表達式推斷,推斷方式與用auto聲明變數相同。

使用標準函數std::move可以使之被用以通過move擷取:

auto ptr = std::make_unique<int>(10); //See below for std::make_unique
auto lambda = [value = std::move(ptr)] {return *value;}

函數返回類型推導

C++11允許lambda函數根據return陳述式的表達式類型推斷返回類型。C++14為一般的函數也提供了這個能力。C++14還拓展了原有的規則,使得函數體並不是{return expression;}形式的函數也可以使用返回類型推導。[8]

為了啟用返回類型推導,函數聲明必須將auto作為返回類型,但沒有C++11的後置返回類型說明符:

auto DeduceReturnType();   //返回类型由编译器推断

如果函數實現中含有多個return陳述式,這些表達式必須可以推斷為相同的類型。[9]

使用返回類型推導的函數可以前向聲明,但在定義之前不可以使用。它們的定義在使用它們的翻譯單元(translation unit)之中必須是可用的。

這樣的函數中可以存在遞歸,但遞歸呼叫必須在函數定義中的至少一個return陳述式之後:[9]

auto Correct(int i) {
  if (i == 1)
    return i;               // 返回类型被推断为int
  else
    return Correct(i-1)+i;  // 正确,可以调用
}

auto Wrong(int i)
{
  if(i != 1)
    return Wrong(i-1)+i;  // 不能调用,之前没有return语句
  else
    return i;             // 返回类型被推断为int
}

另一種類型推斷

C++11中有兩種推斷類型的方式。auto根據給出的表達式產生具有合適類型的變數。decltype可以計算給出的表達式的類型。但是,decltypeauto推斷類型的方式是不同的。特別地,auto總是推斷出非參照類型,就好像使用了std::remove_reference一樣,而auto&&總是推斷出參照類型。然而decltype可以根據表達式的值類別value category)和表達式的性質推斷出參照或非參照類型:[8]

int i;
int&& f();
auto x3a = i;              // x3a的类型是int
decltype(i) x3d = i;       // x3d的类型是int
auto x4a = (i);            // x4a的类型是int
decltype((i)) x4d = (i);   // x4d的类型是int&
auto x5a = f();            // x5a的类型是int
decltype(f()) x5d = f();   // x5d的类型是int&&

C++14增加了decltype(auto)的語法。允許auto的類型聲明使用decltype的規則。也即,允許不必顯式指定作為decltype參數的表達式,而使用decltype對於給定表達式的推斷規則。

    const int x = 0;
    auto x1 = x; // int
    decltype(auto) x2 = x; // const int
    int y = 0;
    int& y1 = y;
    auto y2 = y1; // int
    decltype(auto) y3 = y1; // int&
    int&& z = 0;
    auto z1 = std::move(z); // int
    decltype(auto) z2 = std::move(z); // int&&

decltype(auto)的語法也可以用於返回類型推導,只需用decltype(auto)代替auto[9]

// Return type is `int`.
auto f(const int& i) {
    return i;
}
// Return type is `const int&`.
decltype(auto) g(const int& i) {
    return i;
}
int CPP() 
{  
    // Note: Especially useful for generic code!
    int x = 123;
    static_assert(std::is_same<const int&, decltype(f(x))>::value == 0);
    static_assert(std::is_same<int, decltype(f(x))>::value == 1);
    static_assert(std::is_same<const int&, decltype(g(x))>::value == 1);
    return 0; 
}

放鬆的constexpr函數限制

C++11引入了聲明為constexpr的函數的概念。聲明為constexpr函數的意義是:如果其參數均為合適的編譯期常數,則對這個constexpr函數的呼叫就可用於期望常數表達式的場合(如模板的非類型參數,或列舉常數的值)。如果參數的值在執行期才能確定,或者雖然參數的值是編譯期常數,但不符合這個函數的要求,則對這個函數呼叫的求值只能在執行期進行。 然而C++11要求constexpr函數只含有一個將被返回的表達式(也可以還含有static_assert聲明等其它陳述式,但允許的陳述式類型很少)。

C++14放鬆了這些限制。聲明為constexpr的函數可以含有以下內容:[8]

  • 任何聲明,除了:
    • staticthread_local變數。
    • 沒有初始化的變數聲明。
  • 條件分支陳述式ifswitch
  • 所有的迴圈陳述式,包括基於範圍的for迴圈。
  • 表達式可以改變一個對象的值,只需該對象的生命期在聲明為constexpr的函數內部開始。包括對有constexpr聲明的任何非const非靜態成員函數的呼叫。

goto仍然不允許在constexpr函數中出現。

此外,C++11指出,所有被聲明為constexpr的非靜態成員函數也隱含聲明為const(即函數不能修改*this的值)。這點已經被刪除,非靜態成員函數可以為非const[10]

變數模板

C++之前的版本中,模板可以是函數模板或類別模板(C++11引入了類型別名模板)。C++14現在也可以建立變數模板。在提案中給出的範例是變數pi,其可以被讀取以獲得各種類型的pi的值(例如,當被讀取為整數類型時為3;當被讀取為float,double,long double時,可以是近似floatdoublelong double精度的值)包括特化在內,通常的模板的規則都適用於變數模板的聲明和定義。[6][11]

template<typename T>
constexpr T pi = T(3.141592653589793238462643383);

// 适用于特化规则 :
template<>
constexpr const char* pi<const char*> = "pi";

聚合類別成員初始化

C++11增加了default member initializer,如果建構函式沒有初始化某個成員,並且這個成員擁有default member initializer,就會用default member initializer來初始化成員。聚合類(aggregate type)的定義被改為明確排除任何含有default member initializer的類類型,因此,如果一個類含有default member initializer,就不允許使用聚合初始化。

C++14放鬆了這一限制,[8]含有default member initializer的類型也允許聚合初始化。如果在定義聚合體類型的對象時,使用的花括號初始化列表沒有指定該成員的值,將會用default member initializer初始化它。[12]

struct CXX14_aggregate {
    int x;
    int y = 42;
};

CXX14_aggregate a = {1}; // C++14允许。a.y被初始化为42

二進制字面量

C++14的數字可以用二進制形式指定。[8]其格式使用字首0b0B。這樣的語法也被JavaPythonPerlD語言使用。

數字分位符

C++14引入單引號(')作為數字分位符號,使得數值型的字面量可以具有更好的可讀性。[13]

AdaD語言JavaPerlRuby等程式語言使用底線(_)作為數字分位符號,C++之所以不和它們保持一致,是因為底線已被用在用戶自訂的字面量的語法中。

auto integer_literal = 100'0000;
auto floating_point_literal = 1.797'693'134'862'315'7E+308;
auto binary_literal = 0b0100'1100'0110;
auto silly_example = 1'0'0'000'00;

deprecated 屬性

deprecated屬性允許標記不推薦使用的實體,該實體仍然能合法使用,但會讓用戶注意到使用它是不受歡迎的,並且可能會導致在編譯期間輸出警告訊息。 deprecated可以有一個可選的字串文字作為參數,以解釋棄用的原因和/或建議替代者。

[[deprecated]] int f();

[[deprecated("g() is thread-unsafe. Use h() instead")]]
void g( int& x );

void h( int& x );

void test() {
  int a = f(); // 警告:'f'已弃用
  g(a); // 警告:'g'已弃用:g() is thread-unsafe. Use h() instead
}

新的標準庫特性

共用的互斥體和鎖

C++14增加了一類共用的互斥體和相應的共用鎖[14][15]。起初選擇的名字是std::shared_mutex,但由於後來增加了與std::timed_mutex相似的特性,std::shared_timed_mutex成為了更適合的名字。[16]

元函數的別名

C++11定義了一組元函數,用於查詢一個給定類型是否具有某種特徵,或者轉換給定類型的某種特徵,從而得到另一個類型。後一種元函數通過成員類型type來返迴轉換後的類型,當它們用在模板中時,必須使用typename關鍵字,這會增加代碼的長度。

template <class T>
type_object<
  typename std::remove_cv<
    typename std::remove_reference<T>::type
  >::type
>get_type_object(T&);

利用類型別名模板,C++14提供了更便捷的寫法。其命名規則是:如果標準庫的某個類別模板(假設為std::some_class)只含有唯一的成員,即成員類型type,那麼標準庫提供std::some_class_t<T>作為typename std::some_class::type的別名。

在C++14,擁有類型別名的元函數包括:remove_const、remove_volatile、remove_cv、add_const、add_volatile、add_cv、remove_reference、add_lvalue_reference、add_rvalue_reference、make_signed、make_unsigned、remove_extent、remove_all_extents、remove_pointer、add_pointer、aligned_storage、aligned_union、decay、enable_if、conditional、common_type、underlying_type、result_of、tuple_element。

template <class T>
type_object<std::remove_cv_t<std::remove_reference_t<T>>>
get_type_object(T&);

關聯容器中的異構尋找

C++標準庫定義了四個關聯容器類。set和multiset允許用戶根據一個值在容器中尋找對應的同類型的值。map和multimap容器允許用戶指定鍵(key)和值(value)的類型,根據鍵進行尋找並返回對應的值。然而,尋找只能接受指定類型的參數,在map和multimap中是鍵的類型,而在set和multiset容器中就是值本身的類型。

C++14允許通過其他類型進行尋找,只需要這個類型和實際的鍵類型之間可以進行比較操作。[17]這允許std::set<std::string>使用const char*,或任何可以通過operator< std::string比較的類型作為尋找的參數。

為保證向下相容性,這種異構尋找只在提供給關聯容器的比較器允許的情況下有效。標準庫的泛型比較器,如std::less<>std::greater<>允許異構尋找。[18]

標準自訂字面量

C++11增加了自訂字面量user-defined literals)的特性,使用戶能夠定義新的字面量字尾,但標準庫並沒有對這一特性加以利用。C++14標準庫定義了以下字面量字尾:[17]

  • "s",用於建立各種std::basic_string類型。
  • "h"、"min"、"s"、"ms"、"us"、"ns",用於建立相應的std::chrono::duration時間間隔。
  • "if"、"i"、"il"用於建立相應的 std::complex<float>、 std::complex<double> 和 std::complex<long double> 複數類型。

這些字面量可用於編譯時的constexpr。

auto str = "hello world"s; // 自动推导为 std::string
auto dur = 60s;            // 自动推导为 chrono::seconds
auto z   = 1i;             // 自动推导为 complex<double>

兩個"s"互不干擾,因為表示字串的只能對字串字面量操作,而表示秒的只針對數字。[19]

通過類型定址多元組

C++11引入的std::tuple類型允許不同類型的值的聚合體用編譯期整型常數索引。C++14還允許使用類型代替常數索引,從多元組中取得對象。[17]若多元組含有多於一個這個類型的對象,將會產生一個編譯錯誤:[20]

tuple<string, string, int> t("foo", "bar", 7);
int i = get<int>(t);        // i == 7
int j = get<2>(t);          // Same as before: j == 7
string s = get<string>(t);  //Compiler error due to ambiguity

較小的標準庫特性

std::make_unique可以像std::make_shared一樣使用,用於產生std::unique_ptr對象。[6]

std::is_final,用於辨識一個class類型是否禁止被繼承

std::integral_constant增加了一個返回常數值的operator()[17]

全域std::begin/std::end函數之外,增加了std::cbegin/std::cend函數,它們總是返回常數迭代器(constant iterators)。

已被移除或是不包含在C++14標準的特性

因為C++14的主要目的是漏洞修復和小的改進,一些重量級的特性被從C++14中移除,其中有部分將加入C++17標準。

關於陣列的擴充

C++11和之前的標準中,在堆疊上分配的陣列被限制為擁有一個固定的、編譯期確定的長度。這一擴充允許在堆疊上分配的一個陣列的最後一維具有執行期確定的長度。[6]

執行期確定長度的陣列不可以作為對象的一部分,也不可以具有全域儲存期,他們只能被聲明為局部變數。執行期確定長度的陣列也可以使用C++11的基於範圍的for迴圈。[21]

同時還將添加std::dynarray類型,它擁有與std::vectorstd::array相似的介面。代表一個固定長度的陣列,其大小在執行期構造對象時確定。std::dynarray類被明顯地設計為當它被放置在棧上時(直接放置在棧上,或作為另一個棧對象的成員),可以使用棧主記憶體而不是堆主記憶體。

由於一些設計無法達成一致,這一擴充已被放棄。

Optional值

類似於C#中的可空類型,optional類型可能含有或不含有一個值。這一類型基於Boostboost::optional類,而添加了C++11和C++14中的新特性,諸如移動和in-place構造。它不允許用在參照類型上。這個類被專門的設計為一個literal type(如果模板參數本身是一個literal type),因此,它在必要的情況下含有constexpr建構函式。[22]

Concepts Lite

被C++11拒絕後,Concepts受到徹底的修改。Concepts Lite是Concepts的一個部分,僅包含類型約束,而不含concept_mapaxiom[23]。 ISO/IEC TS 19217:2015 Information technology -- Programming languages -- C++ Extensions for concepts已出版。

另見

參考資料

  1. ^ Committee Draft, Standard for Programming Language C++ (PDF). [2013-07-25]. (原始內容存檔 (PDF)於2013-08-07). 
  2. ^ Sutter, Herb. We have C++14!. August 18, 2014 [2014-08-18]. (原始內容存檔於2014-08-19). 
  3. ^ josedaniel. C++14 is out!. [2014-09-20]. (原始內容存檔於2014-09-20). 
  4. ^ N3649 Generic (Polymorphic) Lambda Expressions (Revision 3). [2013-07-25]. (原始內容存檔於2013-08-25). 
  5. ^ Move capture in Lambda. [2013-07-25]. (原始內容存檔於2013-01-24). 
  6. ^ 6.0 6.1 6.2 6.3 Sutter, Herb. Trip Report: ISO C++ Spring 2013 Meeting. [2013-06-14]. (原始內容存檔於2017-08-20). 
  7. ^ MSDN: In C++14, you can introduce and initialize new variables in the capture clause, without the need to have those variables exist in the lambda function’s enclosing scope. The initialization can be expressed as any arbitrary expression; the type of the new variable is deduced from the type produced by the expression. One benefit of this feature is that in C++14 you can capture move-only variables (such as std::unique_ptr) from the surrounding scope and use them in a lambda.
  8. ^ 8.0 8.1 8.2 8.3 8.4 Wong, Michael. The View form the C++ Standard meeting April 2013 Part 1. C/C++ Cafe. [14 June 2013]. (原始內容存檔於2013-10-13). 
  9. ^ 9.0 9.1 9.2 N3638 Return type deduction for normal functions. [14 June 2013]. (原始內容存檔於2013-08-25). 
  10. ^ N3652 Relaxing constraints on constexpr functions. [2013-07-25]. (原始內容存檔於2013-08-25). 
  11. ^ N3651 Variable Templates (Revision 1) (PDF). [2013-07-25]. (原始內容存檔 (PDF)於2013-08-25). 
  12. ^ N3653 Member initializers and aggregates. [2013-07-25]. (原始內容存檔於2013-08-25). 
  13. ^ Crowl, Lawrence; Smith, Richard; Snyder, Jeff; Vandevoorde, Daveed. N3781 Single-Quotation-Mark as a Digit Separator (PDF). 25 September 2013 [2014-04-11]. (原始內容存檔 (PDF)於2014-04-13). 
  14. ^ Wong, Michael. The View form the C++ Standard meeting April 2013 Part 3. C/C++ Cafe. [14 June 2013]. (原始內容存檔於2013-10-13). 
  15. ^ N3659 Shared locking in C++ Revision 2. [2013-07-25]. (原始內容存檔於2013-08-19). 
  16. ^ N3891: A proposal to rename shared_mutex to shared_timed_mutex. [2014-04-11]. (原始內容存檔於2014-04-13). 
  17. ^ 17.0 17.1 17.2 17.3 Wong, Michael. The View form the C++ Standard meeting April 2013 Part 2. C/C++ Cafe. [14 June 2013]. (原始內容存檔於2013-10-13). 
  18. ^ N3657 Adding heterogeneous comparison lookup to associative containers (rev 4). [2013-07-25]. (原始內容存檔於2013-08-19). 
  19. ^ N3642 User-defined Literals for Standard Library Types (part 1 - version 4) (PDF). [2013-07-25]. (原始內容存檔 (PDF)於2013-08-25). 
  20. ^ N3670 Wording for Addressing Tuples by Type: Revision 2. [2013-07-25]. (原始內容存檔於2013-08-19). 
  21. ^ N3639 Runtime-sized arrays with automatic storage duration (revision 5). [2013-06-14]. (原始內容存檔於2013-06-24). 
  22. ^ A proposal to add a utility class to represent optional objects (Revision 4). [2013-07-25]. (原始內容存檔於2013-08-25). 
  23. ^ Concepts Lite: Constraining Templates with Predicates (pdf). 2013-03-17 [2014-06-26]. (原始內容存檔 (PDF)於2013-11-10).