程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++11標准教程,11標准教程

C++11標准教程,11標准教程

編輯:C++入門知識

C++11標准教程,11標准教程


C++11,先前被稱作C++0x,即ISO/IEC 14882:2011,是目前的C++編程語言的正式標准。它取代第二版標准ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公開於1998年,第二版於2003年更新,分別通稱C++98以及C++03,兩者差異很小)。新的標准包含核心語言的新機能,而且擴展C++標准程序庫,並入了大部分的C++ Technical Report 1程序庫(數學的特殊函數除外)。最新的消息被公開在 ISO C++ 委員會網站(英文)。

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

2012年2月28日的國際標准草案(N3376)是最接近於現行標准的草案,差異僅有編輯上的修正。

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

目錄
1候選變更
2C++核心語言的擴充
3核心語言的運行期表現強化
3.1右值引用和 move 語義
3.2泛化的常數表示式
3.3對POD定義的修正
4核心語言建構期表現的加強
4.1外部模板
5核心語言使用性的加強
5.1初始化列表
5.2統一的初始化
5.3類型推導
5.4以范圍為基礎的 for 循環
5.5Lambda函數與表示式
5.6另一種的函數語法
5.7對象建構的改良
5.8顯式虛函數重載
5.9空指針
5.10強類型枚舉
5.11角括號
5.12顯式類型轉換子
5.13模板的別名
5.14無限制的unions
6核心語言能力的提升
6.1變長參數模板
6.2新的字符串字面值
6.3用戶自定義的字面值
6.4多任務存儲器模型
6.5thread-local的存儲期限
6.6使用或禁用對象的默認函數
6.7long long int類型
6.8靜態assertion
6.9允許sizeof運算符作用在類型的數據成員上,無須明確的對象
6.10垃圾回收機制
7C++標准程序庫的變更
7.1標准庫組件上的升級
7.2線程支持
7.3多元組類型
7.4散列表
7.5正則表達式
7.6通用智能指針
7.7可擴展的隨機數功能
7.8包裝引用
7.9多態函數對象包裝器
7.10用於元編程的類型屬性
7.11用於計算函數對象返回類型的統一方法
8已被移除或是不包含在 C++11 標准的特色
9被移除或廢棄的特色
10編譯器實現
11關系項目
12參考資料
12.1C++標准委員會文件
12.2文章
13外部鏈接
候選變更
C++的修訂包含核心語言以及標准程序庫。

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

維持與C++98,可能的話還有C之間的穩定性與兼容性;
盡可能不通過核心語言的擴展,而是通過標准程序庫來引進新的特色;
能夠演進編程技術的變更優先;
改進 C++ 以幫助系統以及庫設計,而不是引進只針對特別應用的新特色;
增進類型安全,提供對現行不安全的技術更安全的替代方案;
增進直接對硬件工作的能力與表現;
提供現實世界中問題的適當解決方案;
實行“zero-overhead”原則(某些功能要求的額外支持只有在該功能被使用時才能使用);
使C++易於教授與學習
對初學者的注重被認為是重要的,因為他們構成了計算機程序員的主體。也因為許多初學者不願擴展他們對 C++ 的知識,只限於使用他們對 C++ 專精的部分。此外,考慮到 C++ 被廣泛的使用(包含應用領域和編程風格),即便是最有經驗的程序員在面對新的編程范式時也會成為初學者。

C++核心語言的擴充
C++委員會的主要焦點是在語言核心的發展上。核心語言將被大幅改善的領域包括多線程(或稱為“多線程”)支持、泛型編程、統一的初始化,以及性能表現的加強。

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

核心語言的運行期表現強化
以下的語言機能主要用來提升某些性能表現,像是存儲器或是速度上的表現。

右值引用和 move 語義
在 C++03及之前的標准,臨時對象(稱為右值"R-values",位於賦值運算符之右)無法被改變,在 C 中亦同(且被視為無法和 const T& 做出區分)。盡管在某些情況下臨時對象的確會被改變,甚至也被視為是一個有用的漏洞。

C++11 增加一個新的非常數引用(reference)類型,稱作右值引用(R-value reference),標記為T &&。右值引用所引用的臨時對象可以在該臨時對象被初始化之後做修改,這是為了允許 move 語義。

C++03 性能上被長期被诟病的其中之一,就是其耗時且不必要的深度拷貝。深度拷貝會發生在當對象是以傳值的方式傳遞。舉例而言,std::vector<T> 是內部保存了 C-style 數組的一個包裝,如果一個std::vector<T>的臨時對象被建構或是從函數返回,要將其存儲只能通過生成新的std::vector<T>並且把該臨時對象所有的數據復制進去。該臨時對象和其擁有的內存會被摧毀。(為了討論上的方便,這裡忽略返回值優化)

在 C++11,一個std::vector的 "move 構造函數" 對某個vector的右值引用可以單純地從右值復制其內部 C-style 數組的指針到新的 vector,然後留下空的右值。這個操作不需要數組的復制,而且空的暫時對象的解構也不會摧毀存儲器。傳回vector暫時對象的函數只需要傳回std::vector<T>&&。如果vector沒有 move 構造函數,那麼復制構造函數將被調用,以const std::vector<T> &的正常形式。 如果它確實有 move 構造函數,那麼就會調用 move 構造函數,這能夠免除大幅的存儲器配置。

基於安全的理由,具名的變量將永遠不被認定為右值,即使它是被如此聲明的;為了獲得右值必須使用 std::move<T>()。

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)的某些用語修正,右值引用允許開發者提供完美轉發 (perfect function forwarding)。當與變長參數模板結合,這項能力允許函數模板能夠完美地轉送引數給其他接受這些特定引數的函數。最大的用處在於轉送構造函數參數,創造出能夠自動為這些特定引數調用正確建構式的工廠函數(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。第二點,函數的內容必須依照 "returnexpr" 的形式。第三點,在引數取代後,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。

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

極簡的默認建構式。這可以使用默認建構式語法,例如SomeConstructor() = default;
極簡的復制建構式,可使用默認語法(default syntax)
極簡的賦值運算符,可使用默認語法(default syntax)
極簡的解構式,不可以是虛擬的(virtual)
一個標准布局(standard-layout)的類型或結構符合以下定義:

只有非靜態的(non-static)數據成員,且這些成員也是符合標准布局的類型
對所有non-static成員有相同的訪問控制(public, private, protected)
沒有虛函數
沒有虛擬基類
只有符合標准布局的基類
沒有和第一個定義的non-static成員相同類型的基類
若非沒有帶有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++有構造函數,能夠重復對象的初始化。但單單只有那樣並不足以取代這項特色的所有機能。在C++03中,只允許在嚴格遵守POD的定義和限制條件的結構及類型上使用這項機能,非POD的類型不能使用,就連相當有用的STL容器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 = 0; // 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++ 不允許定義於函數內部的類型被用於模板,所以前述的作法是不可行的。

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 被創建用來存儲中間結果。如同一般的函數,z 的值不會保留到下一次該不具名函數再次被調用時。

如果 lambda 函數沒有傳回值(例如 void ),其回返類型可被完全忽略。

定義在與 lambda 函數相同作用域的變量參考也可以被使用。這種的變量集合一般被稱作 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 的一部分,同時它以引用方式被傳遞入謂詞函數, 因此它的值可被 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 的基礎語法外,又擴充額外的語法。 然而,當 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;
};

auto 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 將加入支持用來防止上述情形產生,並在編譯期而非運行期捕獲此類錯誤。為保持向後兼容,此功能將是選擇性的。其語法如下:

struct Base {
virtual void some_func(float);
};

struct Derived : Base {
virtual void some_func(int) override; // 錯誤格式: Derive::some_func 並沒有 override Base::some_func
virtual void some_func(float) override; // OK:顯式改寫
};
編譯器會檢查基底類型是否存在一虛擬函數,與派生類中帶有聲明override 的虛擬函數,有相同的函數簽名(signature);若不存在,則會回報錯誤。

C++11 也提供指示字final,用來避免類型被繼承,或是基底類型的函數被改寫:

struct Base1 final { };

struct Derived1 : Base1 { }; // 錯誤格式: class Base1 以標明為 final

struct Base2 {
virtual void f() final;
};

struct Derived2 : Base2 {
void f(); // 錯誤格式: Base2::f 以標明為 final
};
以上的示例中,virtual void f() final;聲明一新的虛擬函數,同時也表明禁止派生函數改寫原虛擬函數。

override與final都不是語言關鍵字(keyword),只有在特定的位置才有特別含意,其他地方仍舊可以作為一般指示字(identifier)使用。

空指針
早在 1972 年,C語言誕生的初期,常數 0 帶有常數及空指針的雙重身分。 C 使用 preprocessor macroNULL 表示空指針, 讓 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< typename first, typename second, int third>
class SomeType;

template< typename second>
typedef SomeType<OtherType, second, 5> TypedefName; // 在C++是不合法的
這不能夠通過編譯。

為了定義模板的別名,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 之前, 不論是類模板或是函數模板,都只能按其被聲明時所指定的樣子,接受一組固定數目的模板參數 [note 1]; 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-8,UTF-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存儲期限(在現行的static、dynamic和automatic之外)被作為下個標准而提出。線程區域的存儲期限會借由存儲指定字thread_local來表明。

static對象(生命周期為整個程序的運行期間)的存儲期限可以被thread-local給替代。就如同其他使用static存儲期的變量,thread-local對象能夠以構造函數初始化並以解構式摧毀。

使用或禁用對象的默認函數
在傳統C++中,若用戶沒有提供, 則編譯器會自動為對象生成默認構造函數(default constructor)、 復制構造函數(copy constructor),賦值運算符(copy assignment operatoroperator=) 以及解構式(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),這個術語正被更為易懂的描述所取代。一個運算可以發生 (is sequenced before) 在另一個運算之前; 又或者兩個運算彼此之間沒有順序關系 (are unsequenced)。
export
exception specifications
std::auto_ptr 被std::unique_ptr 取代。
仿函數基類別 (std::unary_function, std::binary_function)、函數指針適配器、類型成員指針適配器以及綁定器 (binder)。
編譯器實現
C++編譯器對C++11新特性的支持情況:

Visual C++ 2010 :C++0x Core Language Features In VC10: The Table
GCC 4.6 : Status of Experimental C++0x Support in GCC 4.6
關系項目
C++ Technical Report 1
C11,C 編程語言的最新標准
C++1y,計劃中的 C++ 標准

 

原文鏈接:http://blog.csdn.net/zhuxianjianqi/article/details/8658169#t25

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved