close

 C++11

C++11,先前被稱作C++0x (發音 "see plus plus oh ex")是目前計畫中的C++程式語言的新標準。它將取代現行的C++標準ISO/IEC 14882,公開於1998年並於2003年更新,通稱C++98以及C++03。新的標準將會包含核心語言的新機能,而且會擴展C++標準程式庫,併入了大部分的C++ Technical Report 1程式庫(數學的特殊函式可能除外)。因為此項標準尚未完成,記載於此條目的可能並不是C++11最新的情況。最新的訊息被公開在ISO C++ 委員會網站(英文)


ISOIEC JTC1/SC22/WG21 C++ 標準委員會計劃是在2010年8月之前完成對最終委員會草案的投票,以及於2011年3月召開的標準會議完成國際標準的最終草案。然而,WG21 預期 ISO 將要花費六個月到一年的時間才能正式發佈新的 C++ 標準。因此計畫在2011年年底,發佈新的 C++ 標準。為了能夠如期完成,委員會決定致力於直至2006年為止的提案,忽略新的提案[1]


國際標準組織ISO/IEC終於在2011年8月12日核定了C++11程式語言標準,並於2011年九月以「ISO/IEC 14882:2011」為名發佈C++新標準,售價352瑞士法郎。C++作者Bjarne Stroustrup在自己網站上提供了標準草案(3242)(PDF),應與最後的標準內容相差不大。C++11標準在2011年8月獲得一致通過,它是1998年以來C++語言的第一次大修訂。


像C++這樣的程式語言,通過一種演化的的過程來發展其定義。這個過程不可避免地將引發與現有程式碼的相容問題,在C++的發展過程中偶爾會發生。不過根據Bjarne Stroustrup(C++的創始人並且是委員會的一員)表示,新的標準將幾乎100%相容於現有標準


候選變更

C++的修訂將包含核心語言以及標準程式庫。

在發展新標準的每個機能上,委員會採取了幾個方向:

  • 維持與C++98,可能的話還有C之間的穩定性與相容性;
  • 儘可能不透過核心語言的擴展,而是透過標準程式庫來引入新機能;
  • 能夠演進編程技術的變更優先;
  • 改進C++以幫助系統以及程式庫設計,而不是引進只針對特別應用的新機能;
  • 增進型別安全,提供對現行不安全的技術更安全的替代方案;
  • 增進直接對硬體工作的能力與表現;
  • 提供現實世界中問題的適當解決方案;
  • 實行「zero-overhead」原則(某些功能要求的額外支援只有在該功能被使用時才能使用);
  • 使C++易於教授與學習

對初學者的注重被認為是重要的,因為他們構成了計算機程式員的主體。也因為許多初學者不願擴展他們對 C++ 的知識,只限於使用他們對 C++ 專精的部分。此外,考慮到 C++ 被廣泛的使用(包含應用領域和編程風格),即便是最有經驗的程式員在面對新的編程範式時也會成為初學者。

C++核心語言的擴充

C++委員會的主要焦點是在語言核心的發展上。因此C++11 的發表日期取決於這部份標準的作業進度。


核心語言的領域將被大幅改善,包括多緒支援、 泛型編程、統一的初始化,以及表現的加強。


在此分成4個區塊來討論核心語言的特色以及變更: 執行期表現強化、建構期表現強化、可用性強化,還有嶄新的機能。某些特色可能會同時屬於多個區塊,但在此僅於其最具代表性的區塊描述該特色。


核心語言的執行期表現強化

以下的語言機能主要用來提升某些效能表現,像是記憶體或是速度上的表現。

 

右值參照與move語意

在標準C++,暫時性變數(稱為右值"R-values",位於賦值運算子之右)可以傳遞給函式,但他們只能以像const &的型別接進來。如此一來,函式就無法判別真正的右值或是以 const &形式傳進來的正規物件。 此外,因為是const &的形式,所以無法更改物件的內容。


C++11 將增加一個新的參照(reference)型別稱作右值參照(R-value reference),標記為typename &&。他們能夠以非常數(non-const)值的方式傳入,允許物件去改動他們。這項修正允許特定物件創造出move語意。


舉例而言,std::vector是內部保存了C-style陣列的包裝,如果一個暫時的vector被建立或是從函式傳回,要將其儲存只能透過生成新的vector並且把所有的右值資料複製進去。然後暫時的vector被摧毀,刪除它保有的資料。


透過右值參照的話,一個std::vector的"move建構式"使用對某個vector的右值參照可以單純地從右值複製其內部C-style陣列的指標到新的vector,然後留下空的右值。這個操作不需要陣列的複製,而且空的暫時物件的解構也不會摧毀記憶體。傳回vector暫時物件的函式只需要傳回std::vector<>&&。如果vector沒有move建構式,那麼複製建構式將被喚起,以const std::vector<> &的正常形式。 如果它確實有move建構式,那麼就會喚起move建構式,這能夠免除大幅的記憶體配置。


基於安全的理由,具名的變數將永遠不被認定為右值,即使它是被如此宣告的;為了獲得右值必須使用程式庫函式std::move()。


bool is_r_value(int &&) { return true; }
bool is_r_value(const int &) { return false; }
 
void test(int && i)
{
    is_r_value(i); // i 為具名變數,即使被宣告成右值也不會被認定是右值。
    is_r_value(std::move<int>(i)); // 使用 std::move<T>() 取得右值。
}

由於右值參照的用語特性以及對於左值參照(L-value references;regular references)的某些用語修正,右值參照允許開發者提供完全的函式轉呼叫。當與不定長參數模板結合,這項能力允許函式模板能夠完美地轉送引數給其他接受這些特定引數的函式。最大的用處在於轉送建構式參數,創造出能夠自動為這些特定引數呼叫正確建構式的工廠函式(factory function)。

 

泛化的常數表示式

C++ 本來就已具備常數表示式(constant expression)的概念。像是 3+4 總是會產生相同的結果並且沒有任何的副作用。常數表示式對編譯器來說是最佳化的機會,編譯器時常在編譯期執行它們並且將值存入程式中。同樣地,在許多場合下,C++ 規格要求使用常數表示式。例如在陣列大小的定義上,以及列舉值(enumerator values)都要求必須是常數表示式。


然而,常數表示式總是在遇上了函式呼叫或是物件建構式時就終結。所以像是以下的例子是不合法的:


int GetFive() {return 5;}
 
int some_value[GetFive() + 5]// 欲產生 10 個整數的陣列。 不合法的 C++ 寫法

這不是合法的 C++,因為 GetFive() + 5 並不是常數表示式。編譯器無從得知 GetFive 實際上在執行期是常數。理論上而言,這個函式可能會影響全域變數,或者呼叫其他的非執行期(non-runtime)常數函式等。


C++11 將會引進關鍵字 constexpr 允許使用者保證函式或是物件建構式是編譯期常數。以上的例子可以被寫成像是下面這樣:


constexpr int GetFive() {return 5;}
 
int some_value[GetFive() + 5]// 欲產生 10 個整數的陣列。合法的C++11寫法

這使得編譯器能夠瞭解並去驗證 GetFive 是個編譯期常數。


對函式使用 constexpr 在函式可以做的事上面加上了非常嚴格的條件。首先,該函式的回返值型別不能為 void。第二點,函式的內容必須依照 "return expr" 的形式。第三點,在引數取代後,expr 必須是個常數表示式。這些常數表示式只能夠呼叫其他被定義為 constexpr 的函式,或是其他常數表示式的資料變數。 最後一點,有著這樣標籤的函式直到在該編譯單元內被定義之前是不能夠被呼叫的。


變數也可以被定義為常數表示式值:


constexpr double forceOfGravity = 9.8;
constexpr double moonGravity = forceOfGravity / 6.0;

常數表示式的資料變數是隱式的常數。他們可以只儲存常數表示式或常數表示式建構式的結果。


為了從使用者自定型別(user-defined type)建構常數表示式的資料變數,建構式也可以被宣告成 constexpr。與常數表示式函式一樣,常數表示式的建構式必須在該編譯單元內使用之前被定義。他必須有著空的函式本體。它必須用常數表示式初始化他的成員(member)。而這種型別的解構式應當是無意義的(trivial),什麼事都不做。


複製 constexpr 建構起來的型別也應該被定義為 constexpr,這樣可以讓他們從常數表示式的函式以值傳回。類別的任何成員函式,像是複製建構式、重載的運算子等等,只要他們符合常數表示式函式的定義,都可以被宣告成 constexpr。這使得編譯器能夠在編譯期進行類別的複製、對他們施行運算等等。


常數表示式函式或建構式,可以以非常數表示式(non-constexpr)參數喚起。就如同 constexpr 整數字面值能夠指派給 non-constexpr 變數,constexpr 函式也可以接受 non-constexpr 參數,其結果儲存於 non-constexpr 變數。constexpr 關鍵字只有當表示式的成員都是 constexpr,才允許編譯期常數性的可能。

 

對POD定義的修正

在標準C++,一個結構(struct)為了能夠被當成plain old data (POD),必須遵守幾條規則。有很好的理由使我們想讓大量的型別符合這種定義,符合這種定義的型別能夠允許產生與C相容的物件佈局(object layout)。然而,C++03的規則太嚴苛了。


C++11將會放寬關於POD的定義。


當class/struct是極簡的(trivial)、屬於標準佈局(standard-layout),以及他的所有非靜態(non-static)成員都是POD時,會被視為POD。

一個極簡的類別或結構符合以下定義:

  1. 極簡的預設建構式。這可以使用預設建構式語法,例如SomeConstructor() = default;
  2. 極簡的複製建構式,可使用預設語法(default syntax)
  3. 極簡的賦值運算子,可使用預設語法(default syntax)
  4. 極簡的解構式,不可以是虛擬的(virtual)

一個標準佈局(standard-layout)的類別或結構符合以下定義:

  1. 只有非靜態的(non-static)資料成員,且這些成員也是符合標準佈局的型別
  2. 對所有non-static成員有相同的存取控制(public, private, protected)
  3. 沒有虛擬函式
  4. 沒有虛擬基礎類別
  5. 只有符合標準佈局的基礎類別
  6. 沒有和第一個定義的non-static成員相同型別的基礎類別
  7. 若非沒有帶有non-static成員的基礎類別,就是最底層(繼承最末位)的類別沒有non-static資料成員而且至多一個帶有non-static成員的基礎類別。基本上,在該類別的繼承體系中只會有一個類別帶有non-static成員。

核心語言建構期表現的加強

外部模板

在標準C++中,只要在編譯單元內遇到被完整定義的模板,編譯器都必須將其具現化(instantiate)。這會大大增加編譯時間,特別是模板在許多編譯單元內使用相同的參數具現化。看起來沒有辦法告訴C++不要引發模板的具現化。


C++11將會引進外部模板這一構想。C++已經有了強制編譯器在特定地點開始具現化的語法:


template class std::vector<MyClass>;

而C++所缺乏的是阻止編譯器在某個編譯單元內具現化模板的能力。C++11 將簡單的擴充前文語法如下:


extern template class std::vector<MyClass>;

這樣就告訴編譯器不要在該編譯單元內將該模板具現化。

 

核心語言使用性的加強

這些特色存在的主要目的是為了使C++能夠更容易使用。 舉凡可以增進型別安全,減少程式碼重複,不易誤用程式碼之類的。

初始化串列

標準C++從C帶來了初始化串列(initializer list)的概念。這個構想是結構或是陣列能夠依據成員在該結構內定義的順序透過給予的一串引數來產生。這些初始化串列是遞迴的,所以結構的陣列或是包含其他結構的結構可以使用它們。這對靜態串列或是僅是把結構初始化為某值而言相當有用。C++有建構式,能夠重複物件的初始化。但單單只有那樣並不足以取代這項特色的所有機能。除了這些物件必須遵守POD的定義的限制條件,標準C++允許在結構或類別上使用這項機能;非POD的型別不能使用,就連相當有用的C++-style容器像是std::vector也不行。

C++11將會把初始化串列的概念綁到型別上,稱作std::initializer_list。這允許建構式或其他函式像參數般地使用初始化串列。舉例來說:


class SequenceClass
{
public:
  SequenceClass(std::initializer_list<int> list);
};

這將允許SequenceClass由一連串的整數建構,就像:


SequenceClass someVar = {1, 4, 5, 6};

這個建構式是種特殊的建構式,稱作初始化串列建構式。有著這種建構式的類別在統一初始化的時候會被特別對待。


類別std::initializer_list<>是個第一級的C++11標準程式庫型別。然而他們只能夠經由C++11編譯器透過{}語法的使用被靜態地建構 。這個串列一經建構便可複製,雖然這只是copy-by-reference。初始化串列是常數;一旦被建立,其成員均不能被改變,成員中的資料也不能夠被變動。


因為初始化串列是真實型別,除了類別建構式之外還能夠被用在其他地方。正規的函式能夠使用初始化串列作為引數。例如:


void FunctionName(std::initializer_list<float> list);
 
FunctionName({1.0f, -3.45f, -0.4f});

標準容器也能夠以這種方式初始化:


vector<string> v = { "xyzzy", "plugh", "abracadabra" };

統一的初始化

標準 C++ 在初始化型別方面有著許多問題。初始化型別有數種方法,而且交換使用時不會都產生相同結果。傳統的建構式語法,看起來像是函式宣告,而且為了能使編譯器不會弄錯必須採取一些步驟。只有集合體和 POD 型別能夠被集合式的初始化(使用 SomeType var = {/*stuff*/};).


C++11 將會提供一種統一的語法初始化任意的物件,它擴充了初始化串列語法:


struct BasicStruct
{
 int x;
 float y;
};
 
struct AltStruct
{
  AltStruct(int _x, float _y) : x(_x), y(_y) {}
 
private:
  int x;
  float y;
};
 
BasicStruct var1{5, 3.2f};
AltStruct var2{2, 4.3f};


var1 的初始化的運作就如同 C-style 的初始化串列。每個公開的變數將被對應於初始化串列的值給初始化。隱式型別轉換會在需要的時候被使用,這裡的隱式型別轉換不會產生範圍縮限 (narrowing)。要是不能夠轉換,編譯便會失敗。(範圍縮限 (narrowing):轉換後的型別無法表示原型別。如將 32-bit 的整數轉換為 16-bit 或 8-bit 整數,或是浮點數轉換為整數。) var2 的初始化則是簡單地呼叫建構式。


統一的初始化建構能夠免除具體指定特定型別的必要:


struct IdString
{
  std::string name;
  int identifier;
};
 
IdString var3{"SomeName", 4};


該語法將會使用 const char * 參數初始化 std::string 。你也可以做像下面的事:


IdString GetString()
{
  return {"SomeName", 4}; // 注意這裡不需要明確的型別
}


統一初始化不會取代建構式語法。仍然會有需要用到建構式語法的時候。如果一個類別擁有初始化串列建構式(TypeName(initializer_list<SomeType>);),而初始化串列符合 sequence 建構式的型別,那麼它比其他形式的建構式的優先權都來的高。C++11 版本的 std::vector 將會有初始化串列建構式。這表示:


std::vector<int> theVec{4};


這將會呼叫初始化串列建構式,而不是呼叫std::vector只接受一個尺寸參數產生相應尺寸 vector 的建構式。要使用這個建構式,使用者必須直接使用標準的建構式語法。


型別推導

在標準 C++(和 C ),使用變數必須明確的指出其型別。然而,隨著模版型別的出現以及模板超編程的技巧,某物的型別,特別是函式定義明確的回返型別,就不容易表示。在這樣的情況下,將中間結果儲存於變數是件困難的事,可能會需要知道特定的超編程程式庫的內部情況。


C++11 提供兩種方法緩解上述所遇到的困難。首先,有被明確初始化的變數可以使用 auto 關鍵字。這會依據該初始化子(initializer)的具體型別產生變數:


auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;

someStrangeCallableType 的型別就是模板函式 boost::bind 對特定引數所回返的型別。作為編譯器語意分析責任的一部份,這個型別能夠簡單地被編譯器決定,但使用者要透過檢視來判斷型別就不是那麼容易的一件事了。


otherVariable 的型別同樣也是定義明確的,但使用者很容易就能判別。它是個 int(整數),就和整數字面值的型別一樣。


除此之外,decltype 能夠被用來在編譯期決定一個表示式的型別。舉例:


int someInt;
decltype(someInt) otherIntegerVariable = 5;

decltype 和 auto 一起使用會更為有用,因為 auto 變數的型別只有編譯器知道。然而 decltype 對於那些大量運用運算子重載和特化的型別的程式碼的表示也非常有用。


auto 對於減少冗贅的程式碼也很有用。舉例而言,程式員不用寫像下面這樣:


for (vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

而可以用更簡短的


for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)

這項差異隨著程式員開始巢狀容器而更為顯著,雖然在這種情況下 typedef 是一個減少程式碼的好方法。


decltype 所表示的型別可以和 auto 推導出來的不同。


#include <vector>
 
int main()
{
  const std::vector<int> v(1);
  auto a = v[0]// a 為 int 型別
  decltype(v[0])b;   // b 為 const int& 型別,即
                      // std::vector<int>::operator[](size_type)const 的回返型別
  auto c = 0;         // c 為 int 型別
  auto d = c;         // d 為 int 型別 
  decltype(c) e;      // e 為 int 型別,c 實體的型別 
  decltype((c)) f = e; // f 為 int& 型別,因為(c)是左值
  decltype(0) g;      // g為int型別,因為0是右值
}

以範圍為基礎的 for 迴圈

Boost C++ 定義了許多"範圍 (range) "的概念。範圍表現有如受控制的串列 (list),持有容器中的兩點。有序容器是範圍概念的超集 (superset),有序容器中的兩個迭代器 (iterator) 也能定義一個範圍。這些概念以及操作的演算法,將被併入 C++11 標準程式庫。不過 C++11 將會以語言層次的支援來提供範圍概念的效用。


for 述句將允許簡單的範圍迭代:


int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
  x *= 2;
}

上面 for 述句的第一部份定義被用來做範圍迭代的變數,就像被宣告在一般 for 迴圈的變數一樣,其作用域僅只於迴圈的範圍。而在":"之後的第二區塊,代表將被迭代的範圍。這樣一來,就有了能夠允許 C-style 陣列被轉換成範圍概念的概念圖。這可以是 std::vector,或是其他符合範圍概念的物件。

 

Lambda函式與表示式

在標準 C++,特別是當使用 C++ 標準程式庫演算法函式諸如 sort 和 find,使用者經常希望能夠在演算法函式呼叫的附近定義述部的函式(predicate function)。語言本身雖然允許在函式內部定義類別。然而這通常既麻煩又冗贅,也阻礙了程式碼的流程。此外,標準 C++ 不允許定義於函式內部的類別被用於模板,所以前述的作法是不可行的。


明顯的解決方案是允許定義lambda表示式以及lambda函式。C++11 將會允許lambda 函式的定義。


一個 lambda 函式可以用如下的方式定義:


[](int x, int y) { return x + y; }

這個不具名函式的回返型別是 decltype(x+y)。只有在 lambda 函式符合"return expression"的形式下,它的回返型別才能被忽略。在前述的情況下,lambda 函式僅能為一個述句。


在一個更為複雜的例子中,回返型別可以被明確的指定如下:


[](int x, int y) -> int { int z = x + y; return z + x; }

本例中,一個暫時的變數 z 被建立用來儲存中間結果。如同一般的函式,中間結果的值只保有在函式本身的作用域。


如果 lambda 函式沒有傳回值(例如 void ),其回返型別可被完全忽略。 定義在與 lambda 函式相同作用域的變數參考也可以被使用。這種的變數集合一般被稱作 closure (閉包)。 closure 被定義與使用如下:


[]  // 沒有定義任何變數。使用未定義變數會導致錯誤。
[x, &y] // x 以傳值方式傳入(預設),y 以傳參考方式傳入。
[&]   // 任何被使用到的外部變數皆隱式地以參考方式加以引用。
[=]   // 任何被使用到的外部變數皆隱式地以傳值方式加以引用。
[&, x]   // x 顯示地以傳值方式加以引用。其餘變數以參考方式加以引用。
[=, &z]   // z 顯示地以參考方式加以引用。其餘變數以傳值方式加以引用。

 

closure 被定義與使用如下:

 

std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
  total += x;
});
std::cout << total;


上例可計算 someList 元素的總和並將其印出。 變數 total 是 lambda 函式 closure 的一部分,同時它也是 stack 變數的參考, 因此它的值可被 lambda 函式改變。


若不使用參照的符號&,則代表變數以傳值的方式傳入 lambda 函式。 讓使用者可以用這種表示法明確區分變數傳遞的方法:傳值,或是傳參考。 由於 lambda 函式可以不在被宣告的地方就地使用(如置入 std::function 物件中); 這種情況下,若變數是以傳參考的方式連結到 closure 中,是無意義甚至是危險的行為。


若 lambda 函式只在定義的作用域使用, 則可以用 [&] 宣告 lambda 函式, 代表所有參照到 stack 中的變數,都是以參考的方式傳入, 不必一一顯式指明:


std::vector<int> someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
  total += x;
});

變數傳入 lambda 函式的方式可能隨實做有所變化,一般期望的方法是 lambda 函式能保留其作用域函式的 stack 指標,藉此存取區域變數。


若使用 [=] 而非 [&],則代表所有的參考的變數都是傳值使用。


對於不同的變數,傳值或傳參考可以混和使用。 比方說,使用者可以讓所有的變數都以傳參考的方式使用,但帶有一個傳值使用的變數:


int total = 0;
int value = 5;
[&, value](int x) { total += (x * value); };

total 是傳參考的方式傳入 lambda 函式,而 value 則是傳值。


若一個 lambda 函式被定義於某類別的成員函式中,會被當作該類別的 friend。像這樣的 lambda 函式可以使用該類別物件的參考,並且能夠存取其內部的成員。


[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };

這只有當該 lambda 函式創建的作用域是在 SomeType 的成員函式內部時才能運作。


在成員函式中指涉物件的 this 指標,必須要顯式的傳入 lambda 函式, 否則成員函式中的 lambda 函式無法使用任何該物件的變數或函式。


[this]() { this->SomePrivateMemberFunction(); };

若是 lambda 函式使用 [&] 或是 [=] 的形式,this在 lambda 函式即為可見。

lambda 函式是編譯器從屬型別的函式物件; 這種型別名稱只有編譯器自己能夠使用。如果使用者希望將 lambda 函式作為參數傳入,該型別必須是模版型別,或是必須創建一個 std::function 去獲取 lambda 的值。使用 auto 關鍵字讓我們能夠在該區域儲存 lambda 函式:


auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
auto myOnheapLambdaFunc = new auto([=] { /*...*/ });

但是,如果 lambda 函式是以參考的方式獲取到它所有的 closure 變數,或者是沒有 closure 變數,那麼所產生的函式物件會被給予一個特殊的型別:std::reference_closure<R(P)>,其中 R(P) 是包含回返型別的函式簽名。比起由 std::function 獲取而來,這會是lambda函式更有效率的代表:


std::reference_closure<void()> myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };
myLambdaFunc();

另一種的函式語法

標準C 函式宣告語法對於C語言已經足夠。 當演化自 C 的 C++ 變的更為複雜,除了 C 的基礎語法外,在必要時必須加以擴充。 考慮到 template function 的宣告,舊式 C 語言的函式宣告語法曝露出許多限制。 下面的範例,不是合法的 C++03:


template< typename LHS, typename RHS> 
  Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret的型別必須是(lhs+rhs)的型別

Ret 的型別由 LHS與RHS的和來決定。 即使使用 C++11 新加入的 decltype 來宣告 AddingFunc 的返回型別,依然不可行。


template< typename LHS, typename RHS> 
  decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //不合法的 C++11

不合法的原因在於lhs 及 rhs 在定義前就出現了。 直到剖析器解析到函數原型的後半部,lhs 與 rhs 才是有意義的。

針對此問題,C++11 引進一種新的函數定義與宣告的語法:


template< typename LHS, typename RHS> 
  auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}

這種語法也能套用到一般的函數定義與宣告:


struct SomeStruct
{
  auto FuncName(int x, int y) -> int;
};
 
[] SomeStruct::FuncName(int x, int y) -> int
{
  return x + y;
}
關鍵字 auto 的使用與其在自動型別推導代表不同的意義。

物件建構的改良

在標準C++中,建構式不能呼叫其它的建構式;每個建構式必須自己初始化所有的成員或是呼叫一個共用的成員函式。基礎類別的建構式不能夠直接作為衍生類別的建構式;就算基類的建構式已經足夠,每個衍伸的類別仍必須實做自己的建構式。類別中non-constant的資料成員不能夠在宣告的地方被初始化,它們只能在建構式中被初始化。 C++11將會提供這些問題的解決方案。


C++11允許建構式呼叫其他建構式,這種做法稱作委託或轉接(delegation)。 僅僅只需要加入少量的代碼,就能讓數個建構式之間達成功能復用(reuse)。Java以及C#都有提供這種功能。C++11 語法如下:


class SomeType {
  int number;
  string name;
  SomeType( int i, string& s ) : number(i), name(s){}
public:
  SomeType( )           : SomeType( 0, "invalid" ){}
  SomeType( int i )     : SomeType( i, "guest" ){}
  SomeType( string& s ) : SomeType( 1, s ){ PostInit(); }
};

C++03中,建構式執行結束代表物件建構完成; 而允許使用轉接建構式的 C++11 則是以"任何"一個建構式結束代表建構完成。 使用轉接的建構式,函式本體中的代碼將於被轉接的建構式完成後繼續執行(如上例的 PostInit())。 若基底類別使用了轉接建構式,則衍生類別的建構式會在"所有"基底類別的建構式都完成後, 才會開始執行。


C++11 允許衍生類別手動繼承基底類別的建構式, 編譯器可以使用基底類別的建構式完成衍生類別的建構。 而將基類的建構式帶入衍生類的動作, 無法選擇性地部分帶入, 要不就是繼承基類全部的建構式,要不就是一個都不繼承(不手動帶入)。 此外,若牽涉到多重繼承,從多個基底類別繼承而來的建構式不可以有相同的函式簽名(signature)。 而衍生類別的新加入的建構式也不可以和繼承而來的基底建構式有相同的函式簽名,因為這相當於重複宣告。


語法如下:


class BaseClass
{
public:
  BaseClass(int iValue);
};
 
class DerivedClass : public BaseClass
{
public:
  using BaseClass::BaseClass;
};

此語法等同於 DerivedClass 宣告一個DerivedClass(int) 的建構式。 同時也因為 DerivedClass 有了一個繼承而來的建構式,所以不會有預設建構式。

另一方面,C++11可以使用以下的語法完成成員初始化:


class SomeClass
{
public:
  SomeClass() {}
  explicit SomeClass(int iNewValue) : iValue(iNewValue) {}
 
private:
  int iValue = 5;
};

若是建構式中沒有設定iValue的初始值,則會採用類別定義中的成員初始化,令iValue初值為5。在上例中,無參數版本的建構式,iValue便採用預設所定義的值; 而帶有一個整數參數的建構式則會以指定的值完成初始化。


成員初始化除了上例中的賦值形式(使用"=")外,也可以採用建構式以及統一形的初始化(uniform initialization,使用"{}")。

 

顯式虛函數重載

在 C++ 裡,在子類別中容易意外的重載虛函數。舉例來說:


struct Base {
    virtual void some_func();
};
 
struct Derived : Base {
    void some_func();
};


Derived::some_func 的真實意圖為何? 程式員真的試圖重載該虛函數,或這只是意外? 這也可能是 base 的維護者在其中加入了一個與Derived::some_func 同名且擁有相同簽名的虛函式。


另一個可能的狀況是,當基類中的虛函式的簽名被改變,子類中擁有舊簽名的函式就不再重載該虛函式。因此,如果程式員忘記修改所有子類,執行期將不會正確呼叫到該虛函式正確的實現。


C++11 將加入支援用來防止上述情形產生,並在編譯期而非執行期捕獲此類錯誤。為保持向後相容,此功能將是選擇性的。其語法如下:


class B {
    typedef B self;
    virtual void some_func1();
    virtual void some_func2(float);
    virtual void some_func3() const;
    virtual long some_func4(int);
    virtual void f();
    virtual void h(int);
    void j(int);
    void k(int);
};
 
class D [[base_check]] : public B {
    using B::j;
    void sone_func1 [[override]] ();  // 錯誤格式: 錯誤的函式名稱
    void some_func2 [[override]] (double); // 錯誤格式: 錯誤的參數型別
    void some_func3 [[override]] (); // 錯誤格式: 沒有加上 cv-qualification
    int some_func4 [[override]] (int); // 錯誤格式: 不符合 B::some_func4 的回返型別
    virtual void f [[override]] (); // 正確: 重載 B::f
    virtual void g(long);      // 引入新的虛函式
    void h(int);               // 錯誤格式: 重載為加上 [[override]]
    virtual void h(double);    // 錯誤格式: 新的虛函式隱藏了 void h(int)
    virtual void h [[hiding]] (char *);  // 正確, 新的虛函式隱藏了 void h(int)
    virtual   int j( double );           // 正確, 使用 using B::j 防止隱藏
    int k( double );           // 錯誤格式: name hiding and no using declaration
    double k [[hiding]] ( char * ); // 正確, hiding is clearly indicated
    double m [[hiding]] ( char * ); // 錯誤格式, hiding is requested, but not present
    typedef D self; // ill-formed, new type definition hides the definition in B
};

一個 class/struct 若帶有 [[base_check]] 屬性,則意謂著任何隱式重載將會導致編譯期錯誤。所有的重載都必須加上 [[override]] 屬性。[[hiding]] 意謂著新函式隱藏了基類的函式。

 

空指標

早在 1972 年,C語言誕生的初期,常數 0 帶有常數及空指標的雙重身份。 C 使用 preprocessor macro NULL 表示空指標, 讓 NULL 及 0 分別代表空指標及常數 0。 NULL 可被定義為 ((void*)0) 或是 0。

 

C++ 並不採用 C 的規則,不允許將 void* 隱式轉換為其他型別的指標。 為了使代碼 char* c = NULL; 能通過編譯,NULL 只能定義為 0。 這樣的決定使得函數多載無法區分代碼的語意:


void foo(char *);
void foo(int);

C++ 建議 NULL 應當定義為 0,所以foo(NULL); 將會呼叫 foo(int), 這並不是程式員想要的行為,也違反了代碼的直觀性。0 的歧義在此處造成困擾。

 

C++11 引入了新的關鍵字來代表空指標常數:nullptr,將空指標和整數 0 的概念拆開。 nullptr 的型別為nullptr_t,能隱式轉換為任何指標或是成員指標的型別,也能和它們進行相等或不等的比較。 而nullptr不能隱式轉換為整數,也不能和整數做比較。

 

為了向下相容,0 仍可代表空指標常數。

 

char* pc = nullptr;     // OK
int * pi = nullptr;     // OK
int    i = nullptr;     // error
 
foo(nullptr);           // 呼叫 foo(char *)

強型別列舉

在標準C++中,列舉型別不是型別安全的。列舉型別被視為整數,這使得兩種不同的列舉型別之間可以進行比較。C++03 唯一提供的安全機制是一個整數或一個列舉型值不能隱式轉換到另一個列舉別型。 此外,列舉所使用整數型別及其大小都由實作方法定義,皆無法明確指定。 最後,列舉的名稱全數暴露於一般範圍中,因此兩個不同的列舉,不可以有相同的列舉名。 (好比 enum Side{ Right, Left }; 和 enum Thing{ Wrong, Right }; 不能一起使用。)


C++11 引進了一種特別的 "列舉類",可以避免上述的問題。使用 enum class 的語法來宣告:


enum class Enumeration
{
  Val1,
  Val2,
  Val3 = 100,
  Val4 /* = 101 */,
};


此種列舉為型別安全的。列舉類別不能隱式地轉換為整數;也無法與整數數值做比較。 (表示式 Enumeration::Val4 == 101 會觸發編譯期錯誤)。


列舉類別所使用型別必須顯式指定。在上面的範例中,使用的是預設型別 int,但也可以指定其他型別:


enum class Enum2 : unsigned int {Val1, Val2};

列舉類別的語彙範圍(scoping)定義於列舉類別的名稱範圍中。 使用列舉類別的列舉名時,必須明確指定其所屬範圍。 由前述列舉類別 Enum2 為例,Enum2::Val1是有意義的表示法, 而單獨的 Val1 則否。


此外,C++11 允許為傳統的列舉指定使用型別:


enum Enum3 : unsigned long {Val1 = 1, Val2};


列舉名 Val1 定義於 Enum3 的列舉範圍中(Enum3::Val1),但為了相容性, Val1 仍然可以於一般的範圍中單獨使用。


在 C++11 中,列舉類別的前置聲明 (forward declaration) 也是可行的,只要使用可指定型別的新式列舉即可。 之前的 C++ 無法寫出列舉的前置宣告,是由於無法確定列舉變數所佔的空間大小, C++11 解決了這個問題:


enum Enum1;                     // 不合法的 C++ 與 C++11; 無法判別大小
enum Enum2 : unsigned int;      // 合法的 C++11
enum class Enum3;               // 合法的 C++11,列舉類別使用預設型別 int 
enum class Enum4: unsigned int; // 合法的 C++11
enum Enum2 : unsigned short;    // 不合法的 C++11,Enum2 已被宣告為 unsigned int

角括號

標準 C++ 的剖析器一律將 ">>" 視為右移運算子。 但在樣板定義式中,絕大多數的場合其實都代表兩個連續右角括號。 為了避免剖析器誤判,撰碼時不能把右角括號連著寫。


C++11 變更了剖析器的解讀規則;當遇到連續的右角括號時,優先解析右角括號為樣板引數的結束符號。 如果解讀過程中出現普通括號("(" 與 ")"),這條規則產生變化:


template<bool bTest> SomeType;
std::vector<SomeType<1>2>> x1;   // 解讀為 std::vector of "SomeType<true> 2>",
                                 // 非法的表示式, 整數 1 被轉換為 bool 型別 true
std::vector<SomeType<(1>2)>> x1; // 解讀為 std::vector of "SomeType<false>",
                                 // 合法的 C++11 表示式, (1>2) 被轉換為 bool 型別 false

顯式型別轉換子

C++ 為了避免用戶自定的單引數建構式被當成隱式型別轉換子,引入了關鍵字 explicit 修飾字。 但是,在編譯器對物件調用隱式型別轉換的部分,則沒有任何著墨。 比方說,一個 smart pointer 類別具有一個operator bool(), 被定義成若該 smart pointer 保管任何資源或指標,則傳回 true,反之傳回 false。 遇到這樣的代碼時:if(smart_ptr_variable),編譯器可以藉由 operator bool() 隱式轉換成布林值, 和測試原生指標的方法一樣。 但是這類隱式轉換同樣也會發生在非預期之處。由於 C++ 的 bool 型別也是算數型別,能隱式換為整數甚至是浮點數。 拿物件轉換出的布林值做布林運算以外的數學運算,往往不是程式員想要的。


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

 

模板的別名

在進入這個主題之前,各位應該先弄清楚「模板」和「型別」本質上的不同。class template (類別模板,是模板)是用來產生 template class (模板類別,是型別)。
在標準 C++,typedef 可定義模板類別一個新的型別名稱,但是不能夠使用 typedef 來定義模板的別名。舉例來說:


template<bool bTest> SomeType;
std::vector<SomeType<1>2>> x1;   // 解讀為 std::vector of "SomeType<true> 2>",
                                 // 非法的表示式, 整數 1 被轉換為 bool 型別 true
std::vector<SomeType<(1>2)>> x1; // 解讀為 std::vector of "SomeType<false>",
                                 // 合法的 C++11 表示式, (1>2) 被轉換為 bool 型別 false

這不能夠通過編譯。


為了定義模板的別名,C++11 將會增加以下的語法:


template< typename first, typename second, int third>
class SomeType;
 
template< typename second>
using TypedefName = SomeType<OtherType, second, 5>;

using 也能在 C++11 中定義一般型別的別名,等同 typedef:

typedef void (*PFD)(double);            // 傳統語法
using PFD = void (*)(double);         // 新增語法

無限制的unions

在標準 C++ 中,並非任意的型別都能做為 union 的成員。比方說,帶有 non-trivial 建構式的型別就不能是 union 的成員。在新的標準裡,移除了所有對 union 的使用限制,除了其成員仍然不能是參照型別。 這一改變使得 union 更強大,更有用,也易於使用。[1]


以下為 C++11 中 union 使用的簡單範例:


struct point
{
  point() {}
  point(int x, int y): x_(x), y_(y) {}
  int x_, y_;
};
union
{
  int z;
  double w;
  point p;  // 不合法的 C++; point 有一 non-trivial 建構式
            // 合法的 C++11
};

這一改變僅放寬 union 的使用限制,不會影響既有的舊代碼。

 

核心語言能力的提升

這些機能提供了C++語言能夠做一些事情是以前所不能達成的,或是在以前需要繁瑣的寫法、要求一些不可移植的程式庫。

 

不定長參數模板

在 C++11 之前, 不論是模板類或是模板函式,都只能按其被宣告時所指定的樣子,接受一組固定數目的模板參數; C++11 加入新的表示法,允許任意長度、任意型別的模板參數,不必在定義時將參數的長度固定。


template<typename... Values> class tuple;

模板類 tuple 的物件,能接受不限個數的 typename 作為它的模板引數:


class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;

引數的長度也可以是 0,所以 class tuple<> someInstanceName 這樣的定義也是可以的。


若不希望產生引數長度為 0 的不定長參數模板,則可以採用以下的定義:


template<typename First, typename... Rest> class tuple;

不定長參數模板也能運用到模板函式上。 傳統 C 中的 printf 函式,雖然也能達成不定長度的引數的調用,但其並非型別安全。 以下的範例中,C++11 除了能定義型別安全的不定長引數函式外,還能讓類似 printf 的函式能自然地處理非內建型別的物件。 除了在模板參數中能使用...表示不定長模板參數外,函數參數也使用同樣的表示法代表不定長參數。


template<typename... Params> void printf(const std::string &strFormat, Params... parameters);

其中,Params 與 parameters 分別代表模板與函式的不定長參數集合, 稱之為參數包 (parameter pack)。參數包必須要和算子"..."搭配使用,避免語法上的歧義。


不定長參數模板中,不定長參數包無法如同一般參數在類或函式中使用; 因此典型的手法是以遞迴的方法取出可用參數,參看以下的 C++11 printf 範例:


void printf(const char *s)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
      throw std::runtime_error("invalid format string: missing arguments");
    std::cout << *s++;
  }
}
 
template<typename T, typename... Args>
void printf(const char* s, T value, Args... args)
{
  while (*s)
  {
    if (*s == '%' && *(++s) != '%')
    {
      std::cout << value;
      printf(*s ? ++s : s, args...); // 即便当 *s == 0 也会产生调用,以检测更多的类型参数。
      return;
    }
    std::cout << *s++;
  }
  throw std::logic_error("extra arguments provided to printf");
}

printf 會不斷地遞迴調用自身:函式參數包 args... 在調用時, 會被模板型別匹配分離為 T value和 Args... args。 直到 args... 變為空參數,則會與簡單的 printf(const char *s) 形成匹配,結束遞迴。


另一個例子為計算模板參數的長度,這裡使用相似的技巧展開模板參數包 Args...:


template<>
struct count<> {
    static const int value = 0;
};
 
template<typename T, typename... Args>
struct count<T, Args...> { 
    static const int value = 1 + count<Args...>::value;
};


雖然沒有很簡單的機制能個別依序處理變長參數模板的參數,但使用算子"..."還能在代碼各處對參數包施以更複雜的展開操作。舉例來說,一個模板類的定義:


template <typename... BaseClasses> class ClassName : public BaseClasses...
{
public:
 
   ClassName (BaseClasses&&... baseClasses) : BaseClasses(baseClasses)... {}
}

BaseClasses... 會被展開成類別 ClassName 的基底類; ClassName 的建構式需要所有基底類的左值參照,而每一個基底類都是以傳入的參數做初始化 (BaseClasses(baseClasses)...)。


在函式模板中,不定長參數可以和左值參照搭配,達成引數的完美轉送 (perfect forwarding):


template<typename TypeToConstruct> struct SharedPtrAllocator
{
  template<typename... Args> std::shared_ptr<TypeToConstruct> ConstructWithSharedPtr(Args&&... params)
  {
    return tr1::shared_ptr<TypeToConstruct>(new TypeToConstruct(std::forward<Args>(params)...));
  }
}

參數包 parms 可展開為 TypeToConstruct 建構式的引數。 表示式std::forward<Args>(params) 可將引數的型別資訊保留(利用右值參照),傳入建構式。 而算子"..."則能將前述的表示式套用到每一個參數包中的參數。這種工廠函式(factory function)的手法, 使用 std::shared_ptr 管理配置物件的記憶體,避免了不當使用所產生的記憶體洩漏(memory leaks)。


此外,不定長參數的數量可以藉以下的語法得知:


template<typename ...Args> struct SomeStruct
{
  static const int size = sizeof...(Args);
}

SomeStruct<Type1, Type2>::size 是 2,而 SomeStruct<>::size 會是 0。 (sizeof...(Args) 的結果是編譯期常數。)

 

新的字串字面值

標準C++提供了兩種字串字面值。第一種,包含有雙引號,產生以空字元結尾的const char陣列。第二種有著前標L,產生以空字元結尾的const wchar_t陣列,其中wchar_t代表寬字元。對於Unicode編碼的支援尚付闕如。


為了加強C++編譯器對Unicode的支援,型別char的定義被修改為其大小至少能夠儲存UTF-8的8位元編碼,並且能夠容納編譯器的基本字元集的任何成員。


C++11 將支援三種Unicode編碼方式:UTF-8UTF-16,和UTF-32。除了上述char定義的變更, C++11 將增加兩種新的字元型別:char16_t和char32_t。它們各自被設計用來儲存UTF-16 以及UTF-32的字元。


 

以下展示如何產生使用這些編碼的字串字面值:

 

u8"I'm a UTF-8 string."
u"This is a UTF-16 string."
U"This is a UTF-32 string."


第一個字串的型別是通常的const char[];第二個字串的型別是const char16_t[];第三個字串的型別是const char32_t[]。


當建立Unicode字串字面值時,可以直接在字串內插入Unicode codepoints。C++11提供了以下的語法:


u8"This is a Unicode Character: \u2018."
u"This is a bigger Unicode Character: \u2018."
U"This is a Unicode Character: \u2018."

在'\u'之後的是16個位元的十六進位數值;它不需要'0x'的前標。識別字'\u'代表了一個16位元的Unicode codepoint;如果要輸入32位元的codepoint,使用'\U'和32個位元的十六進位數值。只有有效的Unicode codepoints能夠被輸入。舉例而言,codepoints在範圍U+D800—U+DFFF之間是被禁止的,它們被保留給UTF-16編碼的surrogate pairs。


有時候避免手動將字串換碼也是很有用的,特別是在使用XML檔案或是一些腳本語言的字面值的時候。 C++11將提供raw(未加工的)字串字面值:

R"(The String Data \ Stuff " )" R"delimiter(The String Data \ Stuff " )delimiter"

在第一個例子中,任何包含在[ ]括號當中的都是字串的一部分。其中"和\字元不需要經過跳脫(escaped)。在第二個例子中,"delimiter[開始字串,只有在遇到]delimiter"才代表結束。其中delimiter可以是任意的字串,能夠允許使用者在未加工的字串字面值中使用]字元。 未加工的字串字面值能夠和寬字面值或是Unicode字面值結合:


u8R"XXX(I'm a "raw UTF-8" string.)XXX"
uR"*@(This is a "raw UTF-16" string.)*@"
UR"(This is a "raw UTF-32" string.)"

使用者自訂的字面值

標準C++提供了數種字面值。字元"12.5"是能夠被編譯器解釋為數值12.5的double型別字面值。然而,加上"f"的後置,像是"12.5f",則會產生數值為12.5的float型別字面值。在C++規範中字面值的後置是固定的,而且C++代碼並不允許創立新的字面後置。


C++1x 開放使用者定義新的字面修飾符(literal modifier),利用自訂的修飾符完成由字面值建構物件。


字面值轉換可以區分為兩個階段:轉換前與轉換後 (raw 與 cooked)。 轉換前的字面值指特定字元序列,而轉換後的字面值則代表另一種型別。 如字面值1234,轉換前的字面值代表 '1', '2', '3', '4' 的字元序列; 而轉換後,字面值代表整數值1234。 另外,字面值0xA轉換前是序列'0', 'x', 'A';轉換後代表整數值 10。


多工記憶體模型

參見:內部記憶體模型(computing)


C++標準委員會計劃統一對多緒編程的支援。


這將涉及兩個部分:第一、設計一個可以使多個執行緒在一個行程中共存的內部記憶體模型;第二、為執行緒之間的互動提供支援。第二部分將由程式庫提供支援,更多請看緒程支援


在多個執行緒可能會存取相同內部記憶體的情形下,由一個內部記憶體模型對它們進行排程是非常有必要的。遵守模型規則的程式是被保證正確執行的,但違反規則的程式會發生不可預料的行為,這些行為依賴於編譯器的最佳化和記憶體一致性的問題。

 

thread-local的存儲期限

在多緒環境下,讓各緒程擁有各自的變數是很普遍的。這已經存在於函式的區域變數,但是對於全域和靜態變數都還不行。


新的thread_local存儲期限(在現行的staticdynamicautomatic之外)被作為下個標準而提出。緒程區域的存儲期限會藉由存儲指定字thread_local來表明。


static物件(生命週期為整個程式的執行期間)的存儲期限可以被thread-local給替代。就如同其他使用static存儲期的變數,thread-local物件能夠以建構式初始化並以解構式摧毀。

 

使用或禁用物件的預設函式

在傳統C++中,若使用者沒有提供, 則編譯器會自動為物件生成預設建構式(default constructor)、 複製建構式(copy constructor),賦值運算子(copy assignment operator operator=) 以及解構式(destructor)。另外,C++也為所有的類別定義了數個全域算子(如operator delete及operator new)。當使用者有需要時,也可以提供自訂的版本改寫上述的函式。


問題在於原先的c++無法精確地控制這些預設函數的生成。 比方說,要讓類別不能被拷貝,必須將複製建構式與賦值運算子宣告為private,並不去定義它們。 嘗試使用這些未定義的函式會導致編譯期或連結期的錯誤。 但這種手法並不是一個理想的解決方案。


此外,編譯器產生的預設建構式與使用者定義的建構式無法同時存在。 若使用者定義了任何建構式,編譯器便不會生成預設建構式; 但有時同時帶有上述兩者提供的建構式也是很有用的。 目前並沒有顯式指定編譯器產生預設建構式的方法。

C++11 允許顯式地表明採用或拒用編譯器提供的內建函式。例如要求類別帶有預設建構式,可以用以下的語法:


struct SomeType
{
  SomeType() = default; // 預設建構式的顯式宣告
  SomeType(OtherType value);
};

另一方面,也可以禁止編譯器自動產生某些函式。如下面的例子,類別不可複製:


struct NonCopyable
{
  NonCopyable & operator=(const NonCopyable&) = delete;
  NonCopyable(const NonCopyable&) = delete;
  NonCopyable() = default;
};

禁止類別以operator new配置記憶體:


struct NonNewable
{
  void *operator new(std::size_t) = delete;
};

此種物件只能生成於 stack 中或是當作其他類別的成員,它無法直接配置於 heap 之中,除非使用了與平台相關,不可移植的手法。 (使用 placement new 算子雖然可以在用戶自配置的記憶體上呼叫物件建構式,但在此例中其他形式的 new 算子一併被上述的定義 遮蔽("name hiding"),所以也不可行。)


= delete的宣告(同時也是定義)也能適用於非內建函式, 禁止成員函式以特定的引數呼叫:


struct NoDouble
{
  void f(int i);
  void f(double) = delete;
};

若嘗試以 double 的引數呼叫 f(),將會引發編譯期錯誤, 編譯器不會自動將 double 引數轉型為 int 再呼叫f()。 若要徹底的禁止以非int的引數呼叫f(),可以將= delete與模板相結合:


struct OnlyInt
{
  void f(int i);
  template<class T> void f(T) = delete;
};

long long int型別

在 32 位元系統上,一個 long long int 是保有至少 64 個有效位元的整數型別。C99 將這個型別引入了標準 C 中,目前大多數的 C++ 編譯器也支援這種型別。C++11 將把這種型別添加到標準 C++ 中。

 

靜態assertion

C++提供了兩種方法測試assertion(聲明):巨集assert以及前處理器指令#error。但是這兩者對於模版來說都不合用。巨集在執行期測試assertion,而前處理器指令則在前置處理時測試assertion,這時候模版還未能具現化。所以它們都不適合來測試牽扯到模板參數的相關特性。


新的機能會引進新的方式可以在編譯期測試assertion,只要使用新的關鍵字static_assert。 宣告採取以下的形式:


static_assert( constant-expression, error-message ) ;

這裡有一些如何使用static_assert的例子:


static_assert( 3.14 < GREEKPI && GREEKPI < 3.15, "GREEKPI is inaccurate!" ) ;
template< class T >
struct Check
{
  static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;

當常數表示式值為false時,編譯器會產生相應的錯誤訊息。第一個例子是前處理器指令#error的替代方案;第二個例子會在每個模板類別Check生成時檢查assertion。


靜態assertion在模板之外也是相當有用的。例如,某個演算法的實作依賴於long long型別的大小比int還大,這是標準所不保證的。 這種假設在大多數的系統以及編譯器上是有效的,但不是全部。

 

允許sizeof運算子作用在類別的資料成員上,無須明確的物件

在標準C++,sizeof可以作用在物件以及型別上。但是不能夠做以下的事:


struct SomeType { OtherType member; };
 
sizeof(SomeType::member); // 直接由SomeType型別取得非靜態成員的大小,C++03不行。 C++11允許

這會傳回OtherType的大小。C++03並不允許這樣做,所以會引發編譯錯誤。C++11 將會允許這種使用。

 

垃圾回收機制

是否會自動回收那些無法被使用到 (unreachable) 的動態分配物件由實作決定。

C++標準程式庫的變更

數個新機能將會引進至 C++11 標準程式庫。其中許多可以在現行標準下實作,而另外一些則依賴於(或多或少)新的 C++11 核心語言機能。


新的程式庫的大部分被定義於C++標準委員會的Library Technical Report (稱TR1),於2005年發佈。各式 TR1 的完全或部分實作目前提供在命名空間std::tr1。C++11 會將其移置於命名空間 std 之下。

標準庫元件上的升級

目前的標準庫能受益於 C++11 新增的一些語言特性。舉例來說,對於大部份的標準庫容器而言,像是搬移內含大量元素的容器,或是容器之內對元素的搬移,基於右值參照 (Rvalue reference) 的 move 建構子都能優化前述動作。在適當的情況下,標準庫元件將可利用 C++11  的語言特性進行升級。這些語言特性包含但不侷限以下所列:

  • 右值引用和其相關的 move 支援
  • 支援 UTF-16 編碼,和 UTF-32 字元集
  • 變長參數模板 (與右值引用搭配可以達成完美轉送 (perfect forwarding))
  • 編譯期常數表達式
  • Decltype
  • 顯式型別轉換子
  • 使用或禁用物件的預設函式


此外,自 C++ 標準化之後已經過許多年。現有許多代碼利用到了標準庫; 這同時揭露了部份的標準庫可以做些改良。其中之一是標準庫的記憶體配置器 (allocator)。C++11 將會加入一個基於作用域模型的記憶體配置器來支援現有的模型。

 

緒程支援

雖然 C++11 將會在語言的定義上提供一個記憶體模型以支援緒程,但緒程的使用主要將以 C++11 標準函式庫的方式呈現。


C++11 標準函式庫將會提供類別 thread (std::thread)。若要執行一個緒程,可以建立一個類別 thread 的實體,其初始參數為一個函式物件,以及該函式物件所需要的參數。透過成員函式 std::thread::join() 對緒程會合的支援,一個緒程可以暫停直到其它緒程執行完畢。若有底層平台支援,成員函式std::thread::native_handle() 將可提供對原生緒程物件執行平台特定的操作。


對於緒程間的同步,標準函式庫將會提供適當的互斥鎖 (像是 std::mutex,std::recursive_mutex 等等) 和條件變數 (std::condition_variable 和std::condition_variable_any)。前述同步機制將會以 RAII 鎖 (std::lock_guard 和 std::unique_lock) 和鎖相關演算法的方式呈現,以方便程式員使用。


對於要求高效能,或是極底層的工作,有時或甚至是必須的,我們希望緒程間的通訊能避免互斥鎖使用上的開銷。以原子操作來存取記憶體可以達成此目的。針對不同情況,我們可以透過顯性的記憶體屏障改變該存取記憶體動作的可見性。


對於緒程間非同步的傳輸,C++11 標準函式庫加入了 以及 std::packaged_task 用來包裝一個會傳回非同步結果的函式呼叫。 因為缺少結合數個 future 的功能,和無法判定一組 promise 集合中的某一個 promise 是否完成,futures 此一提案因此而受到了批評。


更進階的緒程支援,如緒程池,已經決定留待在未來的 Technical Report 加入此類支援。更進階的緒程支援不會是 C++11 的一部份,但設想是其最終實現將建立在目前已有的緒程支援之上。


std::async 提供了一個簡便方法以用來執行緒程,並將緒程綁定在 std::future。使用者可以選擇一個工作是要多個緒程上非同步的執行,或是在一個緒程上執行並等待其所需要的資料。預設的情況,實作可以根據底層硬體選擇前面兩個選項的其中之一。另外在較簡單的使用情形下,實作也可以利用緒程池提供支援。

 

多元組型別

多元組是一個內由數個異質物件以特定順序排列而成的資料結構。多元組可被視為是 struct 其資料成員的一般化。


由 TR1 演進而來的 C++11 多元組型別將受益於 C++11 某些特色像是不定長參數模板。TR1 版本的多元組型別對所能容納的物件個數會因實作而有所限制,且實作上需要用到大量的巨集技巧。相反的,C++11 版本的多元組型基本上於對其能容納的物件個數沒有限制。然而,編譯器對於模板實體化的遞迴深度上的限制仍舊影響了元組型別所能容納的物件個數 (這是無法避免的情況); C++11 版本的多元組型不會把這個值讓使用者知道。


使用不定長參數模板,多元組型別的宣告可以長得像下面這樣:


template <class ...Types> class tuple;

底下是一個多元組型別的定義和使用情況:


typedef std::tuple <int, double, long &, const char *> test_tuple;
long lengthy = 12;
test_tuple proof (18, 6.5, lengthy, "Ciao!");
 
lengthy = std::get<0>(proof);  // 將 proof 的第一個元素賦值給 lengthy (索引從零開始起跳)
std::get<3>(proof) = " Beautiful!";  // 修改 proof 的第四個元素

我們可以定義一個多元組型別物件 proof 而不指定其內容,前提是 proof 裡的元素其型別定義了預設建構子 (default constructor)。此外,以一個多元組型別物件賦值給另一個多元組型別物件是可能的,但只有在以下情況: 若這兩個多元組型別相同,則其內含的每一個元素其型別都要定義拷貝建構子 (copy constructor); 否則的話,賦值操作符右邊的多元組其內含元素的型別必須能轉換成左邊的多元組其對應的元素型別,又或者賦值操作符左邊的多元組其內含元素的型別必須定義適當的建構子。


typedef std::tuple< int , double, string       > tuple_1 t1;
typedef std::tuple< char, short , const char * > tuple_2 t2 ('X', 2, "Hola!");
t1 = t2 ;  // 可行。前兩個元素會作型別轉換,
           // 第三個字串元素可由 'const char *' 所建構。

多元組類型物件的比較運算是可行的 (當它們擁有同樣數量的元素)。此外,C++11 提供兩個表達式用來檢查多元組類型的一些特性 (僅在編譯期做此檢查)。

  • std::tuple_size<T>::value 回傳多元組 T 內的元素個數,
  • std::tuple_element<I, T>::type 回傳多元組 T 內的第 I 個元素的型別

雜湊表

在過去,不斷有要求想將雜湊表(無序關聯式容器)引進標準庫。只因為時間上的限制,雜湊表才沒有被標準庫所採納。雖然,雜湊表在最糟情況下(如果出現許多衝突 (collision) 的話)在效能上比不過平衡樹。但實際運用上,雜湊表的表現則較佳。


因為標準委員會還看不到有任何機會能將開放定址法標準化,所以目前衝突僅能透過鏈位址法 (linear chaining) 的方式處理。為避免與第三方函式庫發展的雜湊表發生名稱上的衝突,字首將採用 unordered 而非 hash。


函式庫將引進四種雜湊表,其中差別在於底下兩個特性: 是否接受具相同鍵值的項目 (Equivalent keys),以及是否會將鍵值對映到相對應的資料 (Associated values)。


雜湊表類型有無關聯值接受相同鍵值
std::unordered_set
std::unordered_multiset
std::unordered_map
std::unordered_multimap


上述的類別將滿足對一個容器類別的要求,同時也提供存取其中元素的成員函式: insert, erase, begin, end。


雜湊表不需要對現有核心語言做擴展(雖然,雜湊表的實作將會利用到 C++11 新的語言特性),只會對標頭檔 <functional> 做些許擴展,並引入<unordered_set> 和 <unordered_map> 兩個標頭檔。對於其它現有的類別不會有任何修改。同時,雜湊表也不會依賴其它標準庫的擴展功能。

 

正規表示式

過去許多或多或少標準化的程式庫被建立用來處理正規表示式。有鑑於這些演算法的使用非常普遍,因此標準程式庫將會包含他們,並使用各種物件導向語言的潛力。


這個新的程式庫,被定義於<regex>標頭檔,由幾個新的類別所組成:

  • 正規表示式(樣式)以樣板類 basic_regex 的實體表示
  • 樣式匹配的情況以樣板類 match_results 的實體表示


函式 regex_search 是用來搜尋樣式; 若要搜尋並取代,則要使用函式 regex_replace,該函式會回傳一個新的字串。演算法regex_search 和regex_replace 接受一個正規表示式(樣式)和一個字串,並將該樣式匹配的情況儲存在 struct match_results。


底下描述了 match_results 的使用情況:


const char *reg_esp = "[ ,.\\t\\n;:]" ;  // 分隔字元列表
 
std::regex rgx(reg_esp) ;  // 'regex' 是樣板類 'basic_regex' 以型別為 'char' 
                           // 的參數具現化的實體
std::cmatch match ;  // 'cmatch' 是樣板類 match_results' 以型別為 'const char *'
                     // '的參數具現化的實體
const char *target = "Polytechnic University of Turin " ;
 
// 辨別所有被分隔字元所分隔的字
if( regex_search( target, match, rgx ) )
{
  // 若此種字存在
 
  const size_t n = match.size();
  for( size_t a = 0 ; a < n ; a++ )
  {
    string str( match[a].first, match[a].second ) ;
    cout << str << "\n" ;
  }
}

注意雙反斜線的使用,因為 C++ 將反斜線作為跳脫字元使用。但 C++11 的raw string可以用來避免此一問題。函式庫 <regex> 不需要改動到現有的標頭檔,同時也不需要對現有的語言作擴展。

 

通用智慧指標

這些指標是由 TR1 智慧指標演變而來。注意! 智慧指標是類別而非一般指標。


shared_ptr 是一引用計數 (reference-counted) 指標,其行為與一般 C++ 指標即為相似。在 TR1 的實作中,缺少了一些一般指標所擁有的特色,像是別名或是指標運算。C++11 會將加進前述特色。


一個 shared_ptr 只有在已經沒有任何其它 shared_ptr 指向其原本所指向物件時,才會銷毀該物件。


一個 weak_ptr 指向的是一個被 shared_ptr 所指向的物件。該 weak_ptr 可以用來決定該物件是否已被銷毀。weak_ptr 不能被解參考; 想要存取其內部所保存的指標,只能透過 shared_ptr。有兩種方法可達成此目的。第一,類別 shared_ptr 有一個以 weak_ptr 為參數的建構子。第二,類別 weak_ptr 有一個名為 lock 的成員函式,其返回值為一個 shared_ptr。weak_ptr 並不擁有它所指向的物件,因此不影響該物件的銷毀與否。


底下是一個 shared_ptr 的使用範例:


int main( )
{
    std::shared_ptr<double> p_first(new double) ;
 
    {
        std::shared_ptr<double> p_copy = p_first ;
 
        *p_copy = 21.2;
 
    }  // 此時 'p_copy' 會被銷毀,但動態分配的 double 不會被銷毀。
 
    return 0;  // 此時 'p_first' 會被銷毀,動態分配的 double 也會被銷毀 (因為不再有指針指向它)。
}

auto_ptr 將會被 C++ 標準所廢棄,取而代之的是 unique_ptr。 unique_ptr 提供 auto_ptr 大部份特性,唯一的例外是 auto_ptr 的不安全、隱性的左值搬移。不像 auto_ptr,unique_ptr 可以存放在 C++11 提出的那些能察覺搬移動作的容器之中。

 

可延伸的隨機數功能

C 標準函式庫允許使用rand函式來生成偽隨機數。不過其演算法則取決於各程式庫開法者。 C++ 直接從 C 繼承了這部份,但是 C++11 將會提供產生偽亂數的新方法。

C++11 的隨機數功能分為兩部分: 第一,一個亂數生成引擎,其中包含該生成引擎的狀態,用來產生亂數。第二,一個分佈,這可以用來決定產生亂數的範圍,也可以決定以何種分佈方式產生亂數。亂數生成物件即是由亂數生成引擎和分佈所構成。


不同於 C 標準庫的 rand; 針對產生亂數的機制,C++11 將會提供三種演算法,每一種演算法都有其強項和弱項:

樣板類整數/浮點數品質速度狀態數*
linear_congruential 整數 中等[來源請求] 1
subtract_with_carry 兩者皆可 中等 25
mersenne_twister 整數 624

C++11 將會提供一些標準分佈: uniform_int_distribution (離散型均勻分佈),bernoulli_distribution (伯努利分佈),geometric_distribution (幾何分佈), poisson_distribution (卜瓦松分佈),binomial_distribution (二項分佈),uniform_real_distribution (離散型均勻分佈), exponential_distribution (指數分佈),normal_distribution (常態分佈) 和 gamma_distribution (伽瑪分佈)。


底下描述一個亂數生成物件如何由亂數生成引擎和分佈構成:


std::uniform_int_distribution<int> distribution(0, 99); // 以離散型均勻分佈方式產生 int 亂數,範圍落在 0 到 99 之間
std::mt19937 engine; // 建立亂數生成引擎
auto generator = std::bind(distribution, engine); // 利用 bind 將亂數生成引擎和分布組合成一個亂數生成物件
int random = generator();  // 產生亂數

包裝引用

我們可以透過實體化樣板類 reference_wrapper 得到一個包裝引用 (wrapper reference)。包裝引用類似於一般的引用。對於任意物件,我們可以透過模板類ref 得到一個包裝引用 (至於 constant reference 則可透過 cref 得到)。


當樣板函式需要形參的引用而非其拷貝,這時包裝引用就能派上用場:


// 此函數將得到形參 'r' 的引用並對 r 加一
void f (int &r)  { r++; }
 
// 樣板函式
template<class F, class P> void g (F f, P t)  { f(t); }
 
int main()
{
    int i = 0 ;
    g (f, i) ;  // 實體化 'g<void (int &r), int>' 
                // 'i' 不會被修改
    std::cout << i << std::endl;  // 輸出 0
 
    g (f, std::ref(i));  // 實體化 'g<void(int &r),reference_wrapper<int>>'
                         // 'i' 會被修改
    std::cout << i << std::endl;  // 輸出 1
}

這項功能將加入標頭檔 <utility> 之中,而非透過擴展語言來得到這項功能。

 

多型函式物件包裝器

針對函式物件的多型包裝器(又稱多型函式物件包裝器)在語義和語法上和函式指標相似,但不像函式指標那麼狹隘。只要能被呼叫,且其參數能與包裝器相容的都能以多型函式物件包裝器稱之(函式指標,成員函式指標或仿函式)。


透過以下例子,我們可以瞭解多型函式物件包裝器的特性:


std::function<int (int, int)> func;  // 利用樣板類 'function'
                                     // 建立包裝器
std::plus<int> add;  // 'plus' 被宣告為 'template<class T> T plus( T, T ) ;'
                     // 因此 'add' 的型別是 'int add( int x, int y )'
func = &add;  // 可行。'add' 的型參和回返值型別與 'func' 相符
 
int a = func (1, 2);  // 注意: 若包裝器 'func' 沒有參考到任何函式
                      // 會丟出 'std::bad_function_call' 例外
 
std::function<bool (short, short)> func2 ;
if(!func2) { // 因為尚未賦值與 'func2' 任何函式,此條件式為真
 
    bool adjacent(long x, long y);
    func2 = &adjacent ;  // 可行。'adjacent' 的型參和回返值型別可透過型別轉換進而與 'func2' 相符
 
    struct Test {
        bool operator()(short x, short y);
    };
    Test car;
    func = std::ref(car);  // 樣板類 'std::ref' 回傳一個 struct 'car'
                           // 其成員函式 'operator()' 的包裝
}
func = func2;  // 可行。'func2' 的型參和回返值型別可透過型別轉換進而與 'func' 相符

模板類 function 將定義在標頭檔 <functional>,而不須更動到語言本身。

 

用於元編程的型別屬性

對於那些能自行創建或修改本身或其它程式的程式,我們稱之為元編程。這種行為可以發生在編譯或執行期。C++ 標準委員會已經決定引進一組由模板實現的函式庫,程式員可利用此一函式庫於編譯期進行元編程。


底下是一個以元編程來計算指數的例子:


template<int B, int N>
struct Pow {
    // recursive call and recombination.
    enum{ value = B*Pow<B, N-1>::value };
};
 
template< int B > 
struct Pow<B, 0> { 
    // ''N == 0'' condition of termination.
    enum{ value = 1 };
};
int quartic_of_three = Pow<3, 4>::value;


許多演算法能作用在不同的資料型別; C++ 模板支援泛型,這使得代碼能更緊湊和有用。然而,演算法經常會需要目前作用的資料型別的資訊。這種資訊可以透過型別屬性 (type traits) 於模板實體化時將該資訊萃取出來。


型別屬性能識別一個物件的種類和有關一個型別 (class) (或 struct) 的特徵。標頭檔 <type_traits> 描述了我們能識別那些特徵。


底下的例子說明了模板函式『elaborate'是如何根據給定的資料型別,從而實體化某一特定的演算法 (algorithm.do_it)。


// 演算法一
template< bool B > struct Algorithm {
    template<class T1, class T2> int do_it (T1 &, T2 &)  { /*...*/ }
};
 
// 演算法二
template<> struct Algorithm<true> {
    template<class T1, class T2> int do_it (T1, T2)  { /*...*/ }
};
 
// 根據給定的型別,實體化之後的 'elaborate' 會選擇演算法一或二
template<class T1, class T2> 
int elaborate (T1 A, T2 B) 
{
    // 若 T1 為 int 且 T1 為 float,選用演算法二
    // 其它情況選用演算法一
    return Algorithm<std::is_integral<T1>::value && std::is_floating_point<T2>::value>::do_it( A, B ) ;
}

透過定義在 <type_transform> 的型別屬性,自定的型別轉換是可能的 (在模板中,static_cast 和 const_cast 無法適用所有情況)。


此種編程技巧能寫出優美、簡潔的代碼; 然而除錯是此種編程技巧的弱處: 編譯期的錯誤訊息讓人不知所云,執行期的除錯更是困難。


用於計算函式物件返回型別的統一方法

要在編譯期決定一個樣板仿函式的回返值型別並不容易,特別是當回返值依賴於函式的參數時。舉例來說:


struct Clear {
    int    operator()(int);     // 參數與回返值的型別相同
    double operator()(double);  // 參數與回返值的型別相同
};
 
template <class Obj> 
class Calculus {
public:
    template<class Arg> Arg operator()(Arg& a) const
    {
        return member(a);
    }
private:
    Obj member;
};

實體化樣板類 Calculus<Clear>,Calculus 的仿函式其回返值總是和 Clear 的仿函式其回返值具有相同的型別。然而,若給定類別 Confused:


struct Confused { double operator()(int); // 參數與回返值的型別不相同 int operator()(double); // 參數與回返值的型別不相同 }; 

企圖實體化樣板類 Calculus<Confused> 將導致 Calculus 的仿函式其回返值和類別 Confused 的仿函式其回返值有不同的型別。對於 int 和 double 之間的轉換,編譯器將給出警告。

模板 std::result_of 被TR1 引進且被 C++11 所採納,可允許我們決定和使用一個仿函式其回返值的型別。底下,CalculusVer2 物件使用std::result_of 物件來推導其仿函式的回返值型別:


template< class Obj >
class CalculusVer2 {
public:
    template<class Arg>
    typename std::result_of<Obj(Arg)>::type operator()(Arg& a) const
    { 
        return member(a);
    }
private:
    Obj member;
};

如此一來,在實體化 CalculusVer2<Confused> 其仿函式時,不會有型別轉換,警告或是錯誤發生。


模板 std::result_of 在 TR1 和 C++11 有一點不同。TR1 的版本允許實作在特殊情況下,可以無法決定一個函式呼叫其回返值型別。然而,因為 C++11 支援了decltype,實作被要求在所有情況下,皆能計算出回返值型別。

 

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

預計由 Technical Report 提供支援:

  • 模組
  • 十進制型別
  • 數學專用函式


延後討論:

  • Concepts (概念 (C++))
  • 更完整或必備的垃圾回收支援
  • Reflection
  • Macro Scopes

將被移除或廢棄的特色

  • Sequence Point
  • export
  • exception specifications

關聯項目

參考資料

  1. ^ N2544

C++標準委員會檔案


文章


外部連結

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 Bluelove1968 的頭像
    Bluelove1968

    藍色情懷

    Bluelove1968 發表在 痞客邦 留言(0) 人氣()