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已出版。

另見

參考資料

Wikiwand in your browser!

Seamless Wikipedia browsing. On steroids.

Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.

Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.