国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C++ > 正文

C++11特性

2019-11-10 22:13:54
字體:
來源:轉載
供稿:網友

C++11,先前被稱作C++0x,即ISO/IEC 14882:2011,是C++編程語言的一個標準。它替換第二版標準ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公開于1998年,第二版于2003年更新,分別通稱C++98以及C++03,兩者差異很小),且已被C++14替換。相比于C++03,C++11標準包含核心語言的新機能,而且擴展C++標準程序庫,并入了大部分的C++ Technical Report 1程序庫(數學的特殊函數除外)。 ISO/IEC JTC1/SC22/WG21 C++標準委員會計劃在2010年8月之前完成對最終委員會草案的投票,以及于2011年3月召開的標準會議完成國際標準的最終草案。然而,WG21預期ISO將要花費六個月到一年的時間才能正式發布新的C++標準。為了能夠如期完成,委員會決定致力于直至2006年為止的提案,忽略新的提案[1]。最終于2011年8月12日公布,并于2011年9月出版。

2012年2月28日的國際標準草案[1]是最接近于C++11標準的草案,差異僅有編輯上的修正。

像C++這樣的編程語言,通過一種演化的的過程來發展其定義。這個過程不可避免地將引發與現有代碼的兼容問題,在C++的發展過程中偶爾會發生。不過根據比雅尼·斯特勞斯特魯普(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無限制的unions6核心語言能力的提升6.1可變參數模板6.2新的字符串字面值6.3用戶定義字面量6.4多任務內存模型6.5thread-local的存儲期限6.6使用或禁用對象的默認函數6.7long long int類型6.8靜態assertion6.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,把一個vector的右值引用作為參數std::vector的"move構造函數",可以把右值參數所綁定的vector內部的指向C-style數組的指針復制給新的vector,然后把該指針置null。由于臨時變量不會被再次使用,所以不會有代碼去訪問該null指針;又因為該指針為null,當該臨時對象超出作用域時曾經指向的內部C-style數組所使用的內存不會被釋放。因此,該操作不僅無形中免去了深拷貝的開銷,而且還很安全。

右值引用作為數據類型的引入,使得函數可以重載區分它的參數是值類型、傳統的左值引用還是右值引用。這讓除了標準庫的現有代碼無須任何改動就能等到性能提升。一個回返std::vector<T>的函數的回返類型無須為了調用move構造函數而顯式修改為std::vector<T>&&,因為臨時對象自動作為右值。(但是,如果std::vector<T>是沒有move構造函數的C++03版,由于傳統的左值引用也可以綁定到臨時對象上,因此具有const std::vector<T>&參數的復制構造函數會被調用,導致一次顯著的內存分配。)

出于安全的考慮,推行了一些限制。具名的變量被認定為左值,即使它是被聲明為右值引用數據類型;為了獲得右值必須使用顯式類型轉換,如模板函數std::move<T>()。右值引用所綁定的對象應該只在特定情境下被修改,主要用于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為具名變數,即使被宣告成右值引用類型,i作為實參表達式也不會被認定是右值表達式。    is_r_value(std::move<int&>(i)); // 使用std::move<T>()取得右值。}

由于右值引用的語義特性以及對于左值引用(L-value references;regular references)的某些語義修正,右值引用讓開發者能夠提供函數參數的完美轉發(perfect function forwarding)。當與不定長參數模板結合,這項能力允許函數模板能夠完美地轉送參數給其他接受這些特定參數的函數。最大的用處是轉送構造函數參數,創造出能夠自動為這些特定參數調用正確構造函數的工廠函數(factory function)。這個用法可以在C++標準庫中的emplace_back方法中看到。

泛化的常數表示式[編輯]

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

然而,常數表示式不能含有函數調用或是對象構造函數。所以像是以下的例子是不合法的:

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

這在C++03中是不合法的,因為GetFive() + 5并不是常數表示式。C++03編譯器無從得知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的函數也可以像其他函數一樣用于常量表達式以外的地方,此時不需要滿足后兩點。

C++11之前,可以在常量表達式中使用的的變量必須被聲明為const,用常量表達式來初始化,并且必須是整型或枚舉類型。C++11去除了變量必須是整型或枚舉類型的限制,只要變量使用了constexpr關鍵字來定義:

constexpr double earth_gravitational_acceleration = 9.8;constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;

這些變量都是隱式常量,必須使用常量表達式來初始化。

為了讓用戶自定義類型(user-defined type)參與構造常量表示式,構造函數也可以用constexpr來聲明。與constexpr函數一樣,constexpr構造函數必須在該編譯單元內使用之前被定義。它的函數體必須為空。它必須用常量表示式初始化他的成員(member)。而這種類型的析構函數應當是平凡的(trivial)。

擁有constexpr構造函數的類型的復制構造函數通常也應該被定義為constexpr,以便該類型的對象以值傳遞的方式從constexpr函數回返。該類別的任何成員函數,像是復制構造函數、運算符重載函數等等,只要他們匹配常數表達式函數的定義,都可以被聲明成constexpr。使得編譯器能夠在編譯期進行類別的復制、對他們施行運算等等。

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

對POD定義的修正[編輯]

在C++03中,一個類(class)或結構(struct)要想被作為POD,必須遵守幾條規則。匹配這種定義的類型能夠產生與C兼容的對象內存布局(object layout),而且可以被靜態初始化。但C++03標準嚴格限制了何種類型與C兼容或可以被靜態初始化的,盡管并不存在技術原因導致編譯器無法處理。如果創建一個C++03 POD類型,然后為其添加一個非虛成員函數,這個類型就不再是POD類型了,從而無法被靜態初始化,也不再與C兼容,盡管其內存布局并沒有發生變化。

C++11通過把POD概念劃分成兩個概念:平凡的(trivial)和標準布局(standard-layout),放寬了關于POD的定義。

一個平凡的類型可以被靜態初始化,同時意味著使用memcpy來復制數據是合法的,而無須使用復制構造函數。平凡的類型對象的生命周期開始于其存儲空間被分配時,而不是其構造函數完成時。

一個平凡的的類別或結構匹配以下定義:

平凡的默認構造函數。這可以使用默認構造函數語法,例如SomeConstructor() = default;平凡的復制構造函數和move構造函數,可使用默認語法(default syntax)平凡的賦值運算符和move賦值操作符,可使用默認語法(default syntax)平凡的析構函數,不可以是虛函數(virtual)

只有在類沒有虛基類和虛成員函數時,構造函數才是平凡的。復制構造函數和賦值操作符還額外要求所有非靜態數據成員都是平凡的。

一個匹配標準布局的類封裝成員的方式與C兼容。一個標準布局(standard-layout)的類別或結構匹配以下定義:

只有非靜態的(non-static)數據成員,且這些成員也是匹配標準布局的類型對所有non-static成員有相同的訪問控制(public,private,protected)沒有虛函數沒有虛擬基類只有匹配標準布局的基類沒有和第一個定義的non-static成員相同類型的基類若非沒有帶有non-static成員的基類,就是最底層(繼承最末位)的類別沒有non-static數據成員而且至多一個帶有non-static成員的基類。基本上,在該類別的繼承體系中只會有一個類別帶有non-static成員。

一個類、結構、聯合只有在其是平凡的、匹配標準布局,并且所有非靜態成員和基類都是POD時,才被視為POD。

通過劃分,使得放棄一個特性而不失去另一個成為可能。一個具有復雜的復制和move構造函數的類可能不是平凡的,但是它可能匹配標準布局,從而能與C程序交互。類似地,一個同時具有public和private數據成員的類不匹配標準布局,但它可以是平凡的,從而能夠使用memcpy來復制。

核心語言構造期表現的加強[編輯]

外部模板[編輯]

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

C++11將會引入外部模板這一概念。C++已經有了強制編譯器在特定位置開始實例化的語法:

template class std::vector<MyClass>;

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

extern template class std::vector<MyClass>;

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

使用時,如下例:

 std::vector<MyClass> va;

核心語言使用性的加強[編輯]

這些特色存在的主要目的是為了使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>);),而初始化列表與構造函數的參數類型一致,那么它比其他形式的構造函數的優先權都來的高。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;

decltypeauto一起使用會更為有用,因為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循環[編輯]

for語句將允許簡單的范圍迭代:

int my_array[5] = {1, 2, 3, 4, 5};// double the value of each element in my_array:for (int &x : my_array){  x *= 2;}// similar but also using type inference for array elementsfor (auto &x : my_array) {    x *= 2;}

上面for述句的第一部分定義被用來做范圍迭代的變量,就像被聲明在一般for循環的變量一樣,其作用域僅只于循環的范圍。而在":"之后的第二區塊,代表將被迭代的范圍。這種for語句還可以用于C型數組,初始化列表,和任何定義了begin()end()來回返首尾迭代器的類型。

Lambda函數與表示式[編輯]

在標準C++,特別是當使用C++標準程序庫算法函數諸如sortfind,用戶經常希望能夠在算法函數調用的附近定義一個臨時的述部函數(又稱謂詞函數,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函數被定義于某類別的成員函數中,則可以使用該類別對象的引用,并且能夠訪問其內部的成員。

[](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([=] { /*...*/ });

回返類型后置的函數聲明[編輯]

標準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的類型由LHSRHS相加之后的結果的類型來決定。即使使用C++11新加入的decltype來聲明AddingFunc的回返類型,依然不可行。

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

不合法的原因在于lhsrhs在定義前就出現了。直到剖析器解析到函數原型的后半部,lhsrhs才是有意義的。

針對此問題,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已標明為finalstruct Base2 {    virtual void f() final;};struct Derived2 : Base2 {    void f(); // 錯誤格式:Base2::f已標明為final};

以上的示例中,virtual void f() final;聲明一新的虛擬函數,同時也表明禁止派生函數改寫原虛擬函數。

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

空指針[編輯]

早在1972年,C語言誕生的初期,常數0帶有常數及空指針的雙重身份。 C使用preprocessor macro NULL表示空指針,讓NULL0分別代表空指針及常數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;     // OKint * pi = nullptr;     // OKint    i = nullptr;     // errorfoo(pc);           // 呼叫foo(char *)

強類型枚舉[編輯]

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

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

enum class myEnumeration{  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++11enum class Enum3;               // 合法的C++11,列舉類別使用預設型別int enum class Enum4: unsigned int; // 合法的C++11enum 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型別truestd::vector<SomeType<(1>2)>> x1; // 解讀為std::vector of "SomeType<false>",                                 // 合法的C++11表示式,(1>2)被轉換為bool型別false

顯式類型轉換子[編輯]

C++引入了關鍵字explicit來避免用戶自定的單引用構造函數被當成隱式類型轉換子。但是,卻沒有限制明確定義的類型轉換函數。比方說,一個smart pointer類別具有一個operator bool(),被定義成若該smart pointer不為null則傳回true,反之傳回false。遇到這樣的代碼時:if(smart_ptr_variable),編譯器可以借由operator bool()隱式轉換成布爾值,和測試原生指針的方法一樣。但是這類隱式轉換同樣也會發生在非預期之處。由于C++的bool類型也是算術類型,能隱式換為整數甚至是浮點數。拿對象轉換出的布爾值做布爾運算以外的數學運算,往往不是程序員想要的。

在C++11中,關鍵字explicit修飾符也能套用到類型轉換函數上。如同構造函數一樣,它能避免類型轉換函數被隱式轉換調用。但C++11特別指定,在if條件式、循環、邏輯運算等需要布爾值的地方,將其作為顯式類型轉換,因此即使對應的類型轉換函數被explicit修飾也可以調用。這主要為了解決safe bool問題。

模板的別名[編輯]

在進入這個主題之前,各位應該先弄清楚“模板”和“類型”本質上的不同。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++03中,并非任意的類型都能做為union的成員。比方說,帶有non-trivial 構造函數的類型就不能是union的成員。在新的標準里,移除了所有對union的使用限制,除了其成員仍然不能是引用類型。這一改變使得union更強大,更有用,也易于使用。[2]

但是如果union成員具有非平凡的特殊成員函數,則編譯器不會為union生成對應的特殊成員函數,必須手工定義。

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

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

這一改變僅放寬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);

其中,Paramsparameters分別代表模板與函數的變長參數集合,稱之為參數包(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 valueArgs... args。直到args...變為空參數,則會與簡單的printf(const char *s)形成匹配,結束遞歸。

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

template<typename... args>struct Count{};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_tchar32_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"

在第一個例子中,任何包含在( )括號(標準已經從[]改為())當中的都是字符串的一部分。其中"/字符不需要經過轉義。在第二個例子中,"delimiter(開始字符串,只有在遇到)delimiter"才代表結束。其中delimiter可以是最多16個字符的任意的字符串(包含空字符串),但不能包含空格、控制字符和'('、')'、'/'。原始字符串允許用戶使用圓括號(,),例如R"delimiter((a-z))delimiter"等價于"(a-z)"。原始字符串字面值能夠和寬字面值或是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++11開放用戶定義新的字面修飾符(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 deleteoperator 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標準庫會提供類別threadstd::thread)。若要運行一個線程,可以創建一個類別thread的實體,其初始參數為一個函數對象,以及該函數對象所需要的參數。通過成員函數std::thread::join()對線程會合的支持,一個線程可以暫停直到其它線程運行完畢。若有底層平臺支持,成員函數std::thread::native_handle()將可提供對原生線程對象運行平臺特定的操作。

對于線程間的同步,標準庫將會提供適當的互斥鎖(像是std::mutexstd::recursive_mutex等等)和條件參數(std::condition_variablestd::condition_variable_any)。前述同步機制將會以RAII鎖(std::lock_guardstd::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);否則的話,賦值操作符右邊的多元組其內含元素的類別必須能轉換成左邊的多元組其對應的元素類別,又或者賦值操作符左邊的多元組其內含元素的類別必須定義適當的構造函數。

std::tuple< int , double, string       > t1;std::tuple< char, short , const char * > 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

上述的類別將滿足對一個容器類別的要求,同時也提供訪問其中元素的成員函數:inserterasebeginend

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

正則表達式[編輯]

過去許多或多或少標準化的程序庫被創建用來處理正則表達式。有鑒于這些算法的使用非常普遍,因此標準程序庫將會包含他們,并使用各種面向對象語言的潛力。

這個新的程序庫,被定義于<regex>頭文件,由幾個新的類別所組成:

正則表達式(模式)以模板類basic_regex的實體表示模式匹配的情況以模板類match_results的實體表示

函數regex_search是用來搜索模式;若要搜索并替換,則要使用函數regex_replace,該函數會回傳一個新的字符串。算法regex_searchregex_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_ptrweak_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_ptrunique_ptr提供auto_ptr大部分特性,唯一的例外是auto_ptr的不安全、隱性的左值搬移。不像auto_ptrunique_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> static int do_it (T1 &, T2 &)  { /*...*/ }};// 演算法二template<> struct Algorithm<true> {    template<class T1, class T2> static int do_it (T1, T2)  { /*...*/ }};// 根據給定的型別,實體化之後的'elaborate'會選擇演算法一或二template<class T1, class T2> int elaborate (T1 A, T2 B) {    // 若T1為int且T2為float,選用演算法二    // 其它情況選用演算法一    return Algorithm<std::is_integral<T1>::value && std::is_floating_point<T2>::value>::do_it( A, B ) ;}

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

用于計算函數對象回返類型的統一方法[編輯]

要在編譯期決定一個模板仿函數的回返值類別并不容易,特別是當回返值依賴于函數的參數時。舉例來說:

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的仿函數其回返值有不同的類別。對于intdouble之間的轉換,編譯器將給出警告。

模板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++))更完整或必備的垃圾回收支持ReflectionMacro Scopes

被移除或廢棄的特色[編輯]

循序點(sequence point),這個術語正被更為易懂的描述所替換。一個運算可以發生(is sequenced before)在另一個運算之前;又或者兩個運算彼此之間沒有順序關系(are unsequenced)。exportexception specificationsstd::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 TableVisual C++ 2010與Visual C++ 2012支持的C++11特性的對比列表:C++11 Features (Modern C++)Visual C++ 2013支持的C++11特性的對比列表:C++11 Features (Modern C++)Visual C++ 2015預覽版支持的C++11/14/17特性的對比列表:VC2015 Preview語言特性列表GCC 4.8.1已實現C++11標準的所有主要語言特性:Status of Experimental C++11 Support in GCC 4.8

關系項目[編輯]

C++ Technical Report 1C11,C編程語言的最新標準C++14,C++的最新標準轉自:https://zh.wikipedia.org/wiki/C%2B%2B11
上一篇:c++

下一篇:C++基礎知識

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 平泉县| 崇州市| 沈阳市| 潞西市| 仙桃市| 陆丰市| 醴陵市| 林甸县| 绩溪县| 四平市| 英山县| 澄迈县| 卓尼县| 逊克县| 康定县| 南澳县| 天水市| 旬邑县| 汝州市| 安图县| 阜平县| 乌拉特后旗| 北川| 来宾市| 新源县| 南川市| 长顺县| 淅川县| 阜南县| 隆尧县| 南部县| 青岛市| 资阳市| 谢通门县| 灵丘县| 四川省| 锡林浩特市| 辽宁省| 保靖县| 夏津县| 涟水县|