C++11,先前被稱作C++0x,即ISO/IEC 14882:2011,是C++編程語言的一個(gè)標(biāo)準(zhǔn)。它替換第二版標(biāo)準(zhǔn)ISO/IEC 14882:2003(第一版ISO/IEC 14882:1998公開于1998年,第二版于2003年更新,分別通稱C++98以及C++03,兩者差異很小),且已被C++14替換。相比于C++03,C++11標(biāo)準(zhǔn)包含核心語言的新機(jī)能,而且擴(kuò)展C++標(biāo)準(zhǔn)程序庫,并入了大部分的C++ Technical Report 1程序庫(數(shù)學(xué)的特殊函數(shù)除外)。 ISO/IEC JTC1/SC22/WG21 C++標(biāo)準(zhǔn)委員會計(jì)劃在2010年8月之前完成對最終委員會草案的投票,以及于2011年3月召開的標(biāo)準(zhǔn)會議完成國際標(biāo)準(zhǔn)的最終草案。然而,WG21預(yù)期ISO將要花費(fèi)六個(gè)月到一年的時(shí)間才能正式發(fā)布新的C++標(biāo)準(zhǔn)。為了能夠如期完成,委員會決定致力于直至2006年為止的提案,忽略新的提案[1]。最終于2011年8月12日公布,并于2011年9月出版。
2012年2月28日的國際標(biāo)準(zhǔn)草案[1]是最接近于C++11標(biāo)準(zhǔn)的草案,差異僅有編輯上的修正。
像C++這樣的編程語言,通過一種演化的的過程來發(fā)展其定義。這個(gè)過程不可避免地將引發(fā)與現(xiàn)有代碼的兼容問題,在C++的發(fā)展過程中偶爾會發(fā)生。不過根據(jù)比雅尼·斯特勞斯特魯普(C++的創(chuàng)始人并且是委員會的一員)表示,新的標(biāo)準(zhǔn)將幾乎100%兼容于現(xiàn)有標(biāo)準(zhǔn)。
C++的修訂包含核心語言以及標(biāo)準(zhǔn)程序庫。
在發(fā)展新標(biāo)準(zhǔn)的每個(gè)機(jī)能上,委員會采取了幾個(gè)方向:
維持穩(wěn)定性和與C++98,可能的話還有C之間的兼容性;盡可能不通過核心語言的擴(kuò)展,而是通過標(biāo)準(zhǔn)程序庫來引進(jìn)新的特色;能夠演進(jìn)編程技術(shù)的變更優(yōu)先;改進(jìn)C++以幫助系統(tǒng)以及庫設(shè)計(jì),而不是引進(jìn)只針對特別應(yīng)用的新特色;增進(jìn)類型安全,提供對現(xiàn)行不安全的技術(shù)更安全的替代方案;增進(jìn)直接對硬件工作的能力與表現(xiàn);提供現(xiàn)實(shí)世界中問題的適當(dāng)解決方案;實(shí)行“zero-overhead”原則(某些功能要求的額外支持只有在該功能被使用時(shí)才能使用);使C++易于教授與學(xué)習(xí)關(guān)照初學(xué)者被認(rèn)為是重要的,因?yàn)樗麄儤?gòu)成了計(jì)算機(jī)程序員的主體。也因?yàn)樵S多初學(xué)者不愿擴(kuò)展他們對C++的知識,只限于使用他們對C++專精的部分。此外,考慮到C++被廣泛的使用(包含應(yīng)用領(lǐng)域和編程風(fēng)格),即便是最有經(jīng)驗(yàn)的程序員在面對新的編程范式時(shí)也會成為初學(xué)者。
C++委員會的主要作用之一是改善語言核心。核心語言將被大幅改善的領(lǐng)域包括多線程(或稱為“多線程”)支持、泛型編程、統(tǒng)一的初始化,以及性能表現(xiàn)的加強(qiáng)。
在此分成4個(gè)區(qū)塊來討論核心語言的特色以及變更: 運(yùn)行期表現(xiàn)強(qiáng)化、構(gòu)造期表現(xiàn)強(qiáng)化、可用性強(qiáng)化,還有新的功能。某些特性可能會同時(shí)屬于多個(gè)區(qū)塊,但在此僅于其最具代表性的區(qū)塊描述。
以下的語言機(jī)能主要用來提升某些性能表現(xiàn),像是內(nèi)存或是速度上的表現(xiàn)。
在C++03及之前的標(biāo)準(zhǔn),臨時(shí)對象(稱為右值"R-values",因?yàn)樗鼈兺ǔN挥谫x值運(yùn)算符右側(cè))無法被改變,在C中亦同(且被視為等同于const T&)。盡管如此,在某些情況下臨時(shí)對象仍然可能會被改變,但這種表現(xiàn)也被視為是一個(gè)有用的漏洞。
C++11增加一個(gè)新的非常數(shù)引用(reference)類型,稱作右值引用(R-value reference),標(biāo)記為T &&。右值引用所綁定的臨時(shí)對象可以在該臨時(shí)對象被初始化之后做修改,這是為了允許move語義。
C++03低性能問題的之一,就是在以傳值方式傳遞對象時(shí)隱式發(fā)生的耗時(shí)且不必要的深度拷貝。舉例而言,std::vector<T>本質(zhì)上是一個(gè)C-style數(shù)組及其大小的封裝,如果一個(gè)std::vector<T>的臨時(shí)對象是在函數(shù)內(nèi)部或者函數(shù)回返時(shí)創(chuàng)建,要將其存儲就只能通過生成新的std::vector<T>并且把該臨時(shí)對象所有的數(shù)據(jù)復(fù)制過去(為了討論上的方便,這里忽略回返值優(yōu)化)。然后該臨時(shí)對象會被析構(gòu),其使用的內(nèi)存會被釋放。
在C++11,把一個(gè)vector的右值引用作為參數(shù)std::vector的"move構(gòu)造函數(shù)",可以把右值參數(shù)所綁定的vector內(nèi)部的指向C-style數(shù)組的指針復(fù)制給新的vector,然后把該指針置null。由于臨時(shí)變量不會被再次使用,所以不會有代碼去訪問該null指針;又因?yàn)樵撝羔槥閚ull,當(dāng)該臨時(shí)對象超出作用域時(shí)曾經(jīng)指向的內(nèi)部C-style數(shù)組所使用的內(nèi)存不會被釋放。因此,該操作不僅無形中免去了深拷貝的開銷,而且還很安全。
右值引用作為數(shù)據(jù)類型的引入,使得函數(shù)可以重載區(qū)分它的參數(shù)是值類型、傳統(tǒng)的左值引用還是右值引用。這讓除了標(biāo)準(zhǔn)庫的現(xiàn)有代碼無須任何改動就能等到性能提升。一個(gè)回返std::vector<T>的函數(shù)的回返類型無須為了調(diào)用move構(gòu)造函數(shù)而顯式修改為std::vector<T>&&,因?yàn)榕R時(shí)對象自動作為右值。(但是,如果std::vector<T>是沒有move構(gòu)造函數(shù)的C++03版,由于傳統(tǒng)的左值引用也可以綁定到臨時(shí)對象上,因此具有const std::vector<T>&參數(shù)的復(fù)制構(gòu)造函數(shù)會被調(diào)用,導(dǎo)致一次顯著的內(nèi)存分配。)
出于安全的考慮,推行了一些限制。具名的變量被認(rèn)定為左值,即使它是被聲明為右值引用數(shù)據(jù)類型;為了獲得右值必須使用顯式類型轉(zhuǎn)換,如模板函數(shù)std::move<T>()。右值引用所綁定的對象應(yīng)該只在特定情境下被修改,主要用于move構(gòu)造函數(shù)中。
bool is_r_value(int &&) { return true; }bool is_r_value(const int &) { return false; }void test(int && i){ is_r_value(i); // i為具名變數(shù),即使被宣告成右值引用類型,i作為實(shí)參表達(dá)式也不會被認(rèn)定是右值表達(dá)式。 is_r_value(std::move<int&>(i)); // 使用std::move<T>()取得右值。}由于右值引用的語義特性以及對于左值引用(L-value references;regular references)的某些語義修正,右值引用讓開發(fā)者能夠提供函數(shù)參數(shù)的完美轉(zhuǎn)發(fā)(perfect function forwarding)。當(dāng)與不定長參數(shù)模板結(jié)合,這項(xiàng)能力允許函數(shù)模板能夠完美地轉(zhuǎn)送參數(shù)給其他接受這些特定參數(shù)的函數(shù)。最大的用處是轉(zhuǎn)送構(gòu)造函數(shù)參數(shù),創(chuàng)造出能夠自動為這些特定參數(shù)調(diào)用正確構(gòu)造函數(shù)的工廠函數(shù)(factory function)。這個(gè)用法可以在C++標(biāo)準(zhǔn)庫中的emplace_back方法中看到。
泛化的常數(shù)表示式[編輯]
C++本來就已具備常數(shù)表示式(constant exPRession)的概念。像是3+4總是會產(chǎn)生相同的結(jié)果并且沒有任何的副作用。常數(shù)表示式對編譯器來說是最優(yōu)化的機(jī)會,編譯器時(shí)常在編譯期運(yùn)行它們并且將值存入程序中。同樣地,在許多場合下,C++標(biāo)準(zhǔn)要求使用常數(shù)表示式。例如在數(shù)組大小的定義上,以及枚舉值(enumerator values)都要求必須是常數(shù)表示式。
然而,常數(shù)表示式不能含有函數(shù)調(diào)用或是對象構(gòu)造函數(shù)。所以像是以下的例子是不合法的:
int GetFive() {return 5;}int some_value[GetFive() + 5];// 欲產(chǎn)生10個(gè)整數(shù)的陣列。不合法的C++寫法這在C++03中是不合法的,因?yàn)?code style="font-family:monospace,Courier; color:rgb(0,0,0); background-color:rgb(248,249,250); border:1px solid rgb(234,236,240); padding:1px 4px">GetFive() + 5并不是常數(shù)表示式。C++03編譯器無從得知GetFive實(shí)際上在運(yùn)行期是常數(shù)。理論上而言,這個(gè)函數(shù)可能會影響全域參數(shù),或者調(diào)用其他的非運(yùn)行期(non-runtime)常數(shù)函數(shù)等。
C++11引進(jìn)關(guān)鍵字constexpr允許用戶保證函數(shù)或是對象構(gòu)造函數(shù)是編譯期常數(shù)。以上的例子可以被寫成像是下面這樣:
constexpr int GetFive() {return 5;}int some_value[GetFive() + 5];// 欲產(chǎn)生10個(gè)整數(shù)的陣列。合法的C++11寫法這使得編譯器能夠了解并去驗(yàn)證GetFive是個(gè)編譯期常數(shù)。
用constexpr修飾函數(shù)將限制函數(shù)的行為。首先,該函數(shù)的回返值類型不能為void。第二,函數(shù)的內(nèi)容必須依照"return expr"的形式。第三,在參數(shù)替換后,expr必須是個(gè)常數(shù)表示式。這些常數(shù)表示式只能夠調(diào)用其他被定義為constexpr的函數(shù),或是其他常數(shù)表示式的數(shù)據(jù)參數(shù)。最后,有著這樣修飾符的函數(shù)直到在該編譯單元內(nèi)被定義之前是不能夠被調(diào)用的。
聲明為constexpr的函數(shù)也可以像其他函數(shù)一樣用于常量表達(dá)式以外的地方,此時(shí)不需要滿足后兩點(diǎn)。
C++11之前,可以在常量表達(dá)式中使用的的變量必須被聲明為const,用常量表達(dá)式來初始化,并且必須是整型或枚舉類型。C++11去除了變量必須是整型或枚舉類型的限制,只要變量使用了constexpr關(guān)鍵字來定義:
constexpr double earth_gravitational_acceleration = 9.8;constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;這些變量都是隱式常量,必須使用常量表達(dá)式來初始化。
為了讓用戶自定義類型(user-defined type)參與構(gòu)造常量表示式,構(gòu)造函數(shù)也可以用constexpr來聲明。與constexpr函數(shù)一樣,constexpr構(gòu)造函數(shù)必須在該編譯單元內(nèi)使用之前被定義。它的函數(shù)體必須為空。它必須用常量表示式初始化他的成員(member)。而這種類型的析構(gòu)函數(shù)應(yīng)當(dāng)是平凡的(trivial)。
擁有constexpr構(gòu)造函數(shù)的類型的復(fù)制構(gòu)造函數(shù)通常也應(yīng)該被定義為constexpr,以便該類型的對象以值傳遞的方式從constexpr函數(shù)回返。該類別的任何成員函數(shù),像是復(fù)制構(gòu)造函數(shù)、運(yùn)算符重載函數(shù)等等,只要他們匹配常數(shù)表達(dá)式函數(shù)的定義,都可以被聲明成constexpr。使得編譯器能夠在編譯期進(jìn)行類別的復(fù)制、對他們施行運(yùn)算等等。
常數(shù)表達(dá)式函數(shù)或構(gòu)造函數(shù),可以以非常數(shù)表示式(non-constexpr)作為參數(shù)調(diào)用。就如同constexpr整數(shù)字面值能夠指派給non-constexpr參數(shù),constexpr函數(shù)也可以接受non-constexpr參數(shù),其結(jié)果存儲于non-constexpr參數(shù)。constexpr關(guān)鍵字只有當(dāng)表示式的成員都是constexpr,才允許編譯期常數(shù)性的可能。
對POD定義的修正[編輯]
在C++03中,一個(gè)類(class)或結(jié)構(gòu)(struct)要想被作為POD,必須遵守幾條規(guī)則。匹配這種定義的類型能夠產(chǎn)生與C兼容的對象內(nèi)存布局(object layout),而且可以被靜態(tài)初始化。但C++03標(biāo)準(zhǔn)嚴(yán)格限制了何種類型與C兼容或可以被靜態(tài)初始化的,盡管并不存在技術(shù)原因?qū)е戮幾g器無法處理。如果創(chuàng)建一個(gè)C++03 POD類型,然后為其添加一個(gè)非虛成員函數(shù),這個(gè)類型就不再是POD類型了,從而無法被靜態(tài)初始化,也不再與C兼容,盡管其內(nèi)存布局并沒有發(fā)生變化。
C++11通過把POD概念劃分成兩個(gè)概念:平凡的(trivial)和標(biāo)準(zhǔn)布局(standard-layout),放寬了關(guān)于POD的定義。
一個(gè)平凡的類型可以被靜態(tài)初始化,同時(shí)意味著使用memcpy來復(fù)制數(shù)據(jù)是合法的,而無須使用復(fù)制構(gòu)造函數(shù)。平凡的類型對象的生命周期開始于其存儲空間被分配時(shí),而不是其構(gòu)造函數(shù)完成時(shí)。
一個(gè)平凡的的類別或結(jié)構(gòu)匹配以下定義:
平凡的默認(rèn)構(gòu)造函數(shù)。這可以使用默認(rèn)構(gòu)造函數(shù)語法,例如SomeConstructor() = default;平凡的復(fù)制構(gòu)造函數(shù)和move構(gòu)造函數(shù),可使用默認(rèn)語法(default syntax)平凡的賦值運(yùn)算符和move賦值操作符,可使用默認(rèn)語法(default syntax)平凡的析構(gòu)函數(shù),不可以是虛函數(shù)(virtual)只有在類沒有虛基類和虛成員函數(shù)時(shí),構(gòu)造函數(shù)才是平凡的。復(fù)制構(gòu)造函數(shù)和賦值操作符還額外要求所有非靜態(tài)數(shù)據(jù)成員都是平凡的。
一個(gè)匹配標(biāo)準(zhǔn)布局的類封裝成員的方式與C兼容。一個(gè)標(biāo)準(zhǔn)布局(standard-layout)的類別或結(jié)構(gòu)匹配以下定義:
只有非靜態(tài)的(non-static)數(shù)據(jù)成員,且這些成員也是匹配標(biāo)準(zhǔn)布局的類型對所有non-static成員有相同的訪問控制(public,private,protected)沒有虛函數(shù)沒有虛擬基類只有匹配標(biāo)準(zhǔn)布局的基類沒有和第一個(gè)定義的non-static成員相同類型的基類若非沒有帶有non-static成員的基類,就是最底層(繼承最末位)的類別沒有non-static數(shù)據(jù)成員而且至多一個(gè)帶有non-static成員的基類。基本上,在該類別的繼承體系中只會有一個(gè)類別帶有non-static成員。一個(gè)類、結(jié)構(gòu)、聯(lián)合只有在其是平凡的、匹配標(biāo)準(zhǔn)布局,并且所有非靜態(tài)成員和基類都是POD時(shí),才被視為POD。
通過劃分,使得放棄一個(gè)特性而不失去另一個(gè)成為可能。一個(gè)具有復(fù)雜的復(fù)制和move構(gòu)造函數(shù)的類可能不是平凡的,但是它可能匹配標(biāo)準(zhǔn)布局,從而能與C程序交互。類似地,一個(gè)同時(shí)具有public和private數(shù)據(jù)成員的類不匹配標(biāo)準(zhǔn)布局,但它可以是平凡的,從而能夠使用memcpy來復(fù)制。
核心語言構(gòu)造期表現(xiàn)的加強(qiáng)[編輯]
外部模板[編輯]
在標(biāo)準(zhǔn)C++中,只要在編譯單元內(nèi)遇到被完整定義的模板,編譯器都必須將其實(shí)例化(instantiate)。這會大大增加編譯時(shí)間,特別是模板在許多編譯單元內(nèi)使用相同的參數(shù)實(shí)例化。看起來沒有辦法告訴C++不要引發(fā)模板的實(shí)例化。
C++11將會引入外部模板這一概念。C++已經(jīng)有了強(qiáng)制編譯器在特定位置開始實(shí)例化的語法:
template class std::vector<MyClass>;而C++所缺乏的是阻止編譯器在某個(gè)編譯單元內(nèi)實(shí)例化模板的能力。C++11將簡單地?cái)U(kuò)充前文語法如下:
extern template class std::vector<MyClass>;這樣就告訴編譯器不要在該編譯單元內(nèi)將該模板實(shí)例化。
使用時(shí),如下例:
std::vector<MyClass> va;核心語言使用性的加強(qiáng)[編輯]
這些特色存在的主要目的是為了使C++能夠更容易使用。舉凡可以增進(jìn)類型安全,減少代碼重復(fù),不易誤用代碼之類的。
初始化列表[編輯]
標(biāo)準(zhǔn)C++從C帶來了初始化列表(initializer list)的概念。這個(gè)構(gòu)想是結(jié)構(gòu)或是數(shù)組能夠依據(jù)成員在該結(jié)構(gòu)內(nèi)定義的順序通過給予的一串引用來產(chǎn)生。這些初始化列表是遞歸的,所以結(jié)構(gòu)的數(shù)組或是包含其他結(jié)構(gòu)的結(jié)構(gòu)可以使用它們。這對靜態(tài)列表或是僅是把結(jié)構(gòu)初始化為某值而言相當(dāng)有用。C++有構(gòu)造函數(shù),能夠重復(fù)對象的初始化。但單單只有那樣并不足以替換這項(xiàng)特色的所有機(jī)能。在C++03中,只允許在嚴(yán)格遵守POD的定義和限制條件的結(jié)構(gòu)及類別上使用這項(xiàng)機(jī)能,非POD的類型不能使用,就連相當(dāng)有用的STL容器std::vector也不行。
C++11將會把初始化列表的概念綁到類型上,稱作std::initializer_list。這允許構(gòu)造函數(shù)或其他函數(shù)像參數(shù)般地使用初始化列表。舉例來說:
class SequenceClass{public: SequenceClass(std::initializer_list<int> list);};這將允許SequenceClass由一連串的整數(shù)構(gòu)造,就像:
SequenceClass someVar = {1, 4, 5, 6};這個(gè)構(gòu)造函數(shù)是種特殊的構(gòu)造函數(shù),稱作初始化列表構(gòu)造函數(shù)。有著這種構(gòu)造函數(shù)的類別在統(tǒng)一初始化的時(shí)候會被特別對待。
類別std::initializer_list<>是個(gè)第一級的C++11標(biāo)準(zhǔn)程序庫類型。然而他們只能夠經(jīng)由C++11編譯器通過{}語法的使用被靜態(tài)地構(gòu)造。這個(gè)列表一經(jīng)構(gòu)造便可復(fù)制,雖然這只是copy-by-reference。初始化列表是常數(shù);一旦被創(chuàng)建,其成員均不能被改變,成員中的數(shù)據(jù)也不能夠被變動。
因?yàn)槌跏蓟斜硎钦鎸?shí)類型,除了類別構(gòu)造函數(shù)之外還能夠被用在其他地方。正規(guī)的函數(shù)能夠使用初始化列表作為形參。例如:
void FunctionName(std::initializer_list<float> list);FunctionName({1.0f, -3.45f, -0.4f});標(biāo)準(zhǔn)容器也能夠以這種方式初始化:
vector<string> v = { "xyzzy", "plugh", "abracadabra" };統(tǒng)一的初始化[編輯]
標(biāo)準(zhǔn)C++在初始化類型方面有著許多問題。初始化類型有數(shù)種方法,而且交換使用時(shí)不會都產(chǎn)生相同結(jié)果。傳統(tǒng)的構(gòu)造函數(shù)語法,看起來像是函數(shù)聲明,而且為了能使編譯器不會弄錯(cuò)必須采取一些步驟。只有集合體和POD類型能夠被集合式的初始化(使用SomeType var = {/*stuff*/};)。
C++11將會提供一種統(tǒng)一的語法初始化任意的對象,它擴(kuò)充了初始化列表語法:
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的初始化的運(yùn)作就如同C-style的初始化列表。每個(gè)公開的參數(shù)將被對應(yīng)于初始化列表的值給初始化。隱式類型轉(zhuǎn)換會在需要的時(shí)候被使用,這里的隱式類型轉(zhuǎn)換不會產(chǎn)生范圍縮限(narrowing)。要是不能夠轉(zhuǎn)換,編譯便會失敗。(范圍縮限 (narrowing):轉(zhuǎn)換后的類型無法表示原類型。如將32-bit的整數(shù)轉(zhuǎn)換為16-bit或8-bit整數(shù),或是浮點(diǎn)數(shù)轉(zhuǎn)換為整數(shù)。)var2的初始化則是簡單地調(diào)用構(gòu)造函數(shù)。
統(tǒng)一的初始化構(gòu)造能夠免除具體指定特定類型的必要:
struct IdString{ std::string name; int identifier;};IdString var3{"SomeName", 4};該語法將會使用const char *參數(shù)初始化std::string。你也可以做像下面的事:
IdString GetString(){ return {"SomeName", 4}; // 注意這裡不需要明確的型別}統(tǒng)一初始化不會替換構(gòu)造函數(shù)語法。仍然會有需要用到構(gòu)造函數(shù)語法的時(shí)候。如果一個(gè)類別擁有初始化列表構(gòu)造函數(shù)(TypeName(initializer_list<SomeType>);),而初始化列表與構(gòu)造函數(shù)的參數(shù)類型一致,那么它比其他形式的構(gòu)造函數(shù)的優(yōu)先權(quán)都來的高。C++11版本的std::vector將會有初始化列表構(gòu)造函數(shù)。這表示:
std::vector<int> theVec{4};這將會調(diào)用初始化列表構(gòu)造函數(shù),而不是調(diào)用std::vector只接受一個(gè)尺寸參數(shù)產(chǎn)生相應(yīng)尺寸vector的構(gòu)造函數(shù)。要使用這個(gè)構(gòu)造函數(shù),用戶必須直接使用標(biāo)準(zhǔn)的構(gòu)造函數(shù)語法。
類型推導(dǎo)[編輯]
在標(biāo)準(zhǔn)C++和C,使用參數(shù)必須明確的指出其類型。然而,隨著模版類型的出現(xiàn)以及模板元編程的技巧,某物的類型,特別是函數(shù)定義明確的回返類型,就不容易表示。在這樣的情況下,將中間結(jié)果存儲于參數(shù)是件困難的事,可能會需要知道特定的元編程程序庫的內(nèi)部情況。
C++11提供兩種方法緩解上述所遇到的困難。首先,有被明確初始化的參數(shù)可以使用auto關(guān)鍵字。這會依據(jù)該初始化子(initializer)的具體類型產(chǎn)生參數(shù):
auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);auto otherVariable = 5;someStrangeCallableType的類型就是模板函數(shù)boost::bind對特定引用所回返的類型。作為編譯器語義分析責(zé)任的一部分,這個(gè)類型能夠簡單地被編譯器決定,但用戶要通過查看來判斷類型就不是那么容易的一件事了。
otherVariable的類型同樣也是定義明確的,但用戶很容易就能判別。它是個(gè)int(整數(shù)),就和整數(shù)字面值的類型一樣。
除此之外,decltype能夠被用來在編譯期決定一個(gè)表示式的類型。舉例:
int someInt;decltype(someInt) otherIntegerVariable = 5;decltype和auto一起使用會更為有用,因?yàn)閍uto參數(shù)的類型只有編譯器知道。然而decltype對于那些大量運(yùn)用運(yùn)算符重載和特化的類型的代碼的表示也非常有用。
auto對于減少冗贅的代碼也很有用。舉例而言,程序員不用寫像下面這樣:
for (vector<int>::const_iterator itr = myvec.cbegin(); itr != myvec.cend(); ++itr)而可以用更簡短的
for (auto itr = myvec.cbegin(); itr != myvec.cend(); ++itr)這項(xiàng)差異隨著程序員開始嵌套容器而更為顯著,雖然在這種情況下typedef是一個(gè)減少代碼的好方法。
decltype所表示的類型可以和auto推導(dǎo)出來的不同。
#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實(shí)體的型別 decltype((c)) f = e; // f為int&型別,因?yàn)椋╟)是左值 decltype(0) g; // g為int型別,因?yàn)?是右值}基于范圍的for循環(huán)[編輯]
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循環(huán)的變量一樣,其作用域僅只于循環(huán)的范圍。而在":"之后的第二區(qū)塊,代表將被迭代的范圍。這種for語句還可以用于C型數(shù)組,初始化列表,和任何定義了begin()和end()來回返首尾迭代器的類型。
Lambda函數(shù)與表示式[編輯]
在標(biāo)準(zhǔn)C++,特別是當(dāng)使用C++標(biāo)準(zhǔn)程序庫算法函數(shù)諸如sort和find,用戶經(jīng)常希望能夠在算法函數(shù)調(diào)用的附近定義一個(gè)臨時(shí)的述部函數(shù)(又稱謂詞函數(shù),predicate function)。由于語言本身允許在函數(shù)內(nèi)部定義類別,可以考慮使用函數(shù)對象,然而這通常既麻煩又冗贅,也阻礙了代碼的流程。此外,標(biāo)準(zhǔn)C++不允許定義于函數(shù)內(nèi)部的類別被用于模板,所以前述的作法是不可行的。
C++11對lambda的支持可以解決上述問題。
一個(gè)lambda函數(shù)可以用如下的方式定義:
[](int x, int y) { return x + y; }這個(gè)不具名函數(shù)的回返類型是decltype(x+y)。只有在lambda函數(shù)匹配"return expression"的形式下,它的回返類型才能被忽略。在前述的情況下,lambda函數(shù)僅能為一個(gè)述句。
在一個(gè)更為復(fù)雜的例子中,回返類型可以被明確的指定如下:
[](int x, int y) -> int { int z = x + y; return z + x; }本例中,一個(gè)臨時(shí)的參數(shù)z被創(chuàng)建用來存儲中間結(jié)果。如同一般的函數(shù),z的值不會保留到下一次該不具名函數(shù)再次被調(diào)用時(shí)。
如果lambda函數(shù)沒有傳回值(例如void),其回返類型可被完全忽略。
定義在與lambda函數(shù)相同作用域的參數(shù)引用也可以被使用。這種的參數(shù)集合一般被稱作closure(閉包)。
[] // 沒有定義任何變量。使用未定義變量會引發(fā)錯(cuò)誤。[x, &y] // x以傳值方式傳入(默認(rèn)),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;上例可計(jì)算someList元素的總和并將其印出。參數(shù)total是lambda函數(shù)closure的一部分,同時(shí)它以引用方式被傳遞入謂詞函數(shù),因此它的值可被lambda函數(shù)改變。
若不使用引用的符號&,則代表參數(shù)以傳值的方式傳入lambda函數(shù)。讓用戶可以用這種表示法明確區(qū)分參數(shù)傳遞的方法:傳值,或是傳引用。由于lambda函數(shù)可以不在被聲明的地方就地使用(如置入std::function對象中); 這種情況下,若參數(shù)是以傳引用的方式鏈接到closure中,是無意義甚至是危險(xiǎn)的行為。
若lambda函數(shù)只在定義的作用域使用,則可以用[&]聲明lambda函數(shù),代表所有引用到stack中的參數(shù),都是以引用的方式傳入,不必一一顯式指明:
std::vector<int> someList;int total = 0;std::for_each(someList.begin(), someList.end(), [&](int x) { total += x;});參數(shù)傳入lambda函數(shù)的方式可能隨實(shí)現(xiàn)有所變化,一般期望的方法是lambda函數(shù)能保留其作用域函數(shù)的stack指針,借此訪問區(qū)域參數(shù)。
若使用[=]而非[&],則代表所有的引用的參數(shù)都是傳值使用。
對于不同的參數(shù),傳值或傳引用可以混和使用。比方說,用戶可以讓所有的參數(shù)都以傳引用的方式使用,但帶有一個(gè)傳值使用的參數(shù):
int total = 0;int value = 5;[&, value](int x) { total += (x * value); };total是傳引用的方式傳入lambda函數(shù),而value則是傳值。
若一個(gè)lambda函數(shù)被定義于某類別的成員函數(shù)中,則可以使用該類別對象的引用,并且能夠訪問其內(nèi)部的成員。
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction(); };這只有當(dāng)該lambda函數(shù)創(chuàng)建的作用域是在SomeType的成員函數(shù)內(nèi)部時(shí)才能運(yùn)作。
在成員函數(shù)中指涉對象的this指針,必須要顯式的傳入lambda函數(shù),否則成員函數(shù)中的lambda函數(shù)無法使用任何該對象的參數(shù)或函數(shù)。
[this]() { this->SomePrivateMemberFunction(); };若是lambda函數(shù)使用[&]或是[=]的形式,this在lambda函數(shù)即為可見。
lambda函數(shù)是編譯器從屬類型的函數(shù)對象;這種類型名稱只有編譯器自己能夠使用。如果用戶希望將lambda函數(shù)作為參數(shù)傳入,該類型必須是模版類型,或是必須創(chuàng)建一個(gè)std::function去獲取lambda的值。使用auto關(guān)鍵字讓我們能夠存儲lambda函數(shù):
auto myLambdaFunc = [this]() { this->SomePrivateMemberFunction(); };auto myOnheapLambdaFunc = new auto([=] { /*...*/ });回返類型后置的函數(shù)聲明[編輯]
標(biāo)準(zhǔn)C函數(shù)聲明語法對于C語言已經(jīng)足夠。演化自C的C++除了C的基礎(chǔ)語法外,又?jǐn)U充額外的語法。然而,當(dāng)C++變得更為復(fù)雜時(shí),它暴露出許多語法上的限制,特別是針對函數(shù)模板的聲明。下面的示例,不是合法的C++03:
template< typename LHS, typename RHS> Ret AddingFunc(const LHS &lhs, const RHS &rhs) {return lhs + rhs;} //Ret的型別必須是(lhs+rhs)的型別Ret的類型由LHS與RHS相加之后的結(jié)果的類型來決定。即使使用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在定義前就出現(xiàn)了。直到剖析器解析到函數(shù)原型的后半部,lhs與rhs才是有意義的。
針對此問題,C++11引進(jìn)一種新的函數(shù)定義與聲明的語法:
template< typename LHS, typename RHS> auto AddingFunc(const LHS &lhs, const RHS &rhs) -> decltype(lhs+rhs) {return lhs + rhs;}這種語法也能套用到一般的函數(shù)定義與聲明:
struct SomeStruct{ auto FuncName(int x, int y) -> int;};auto SomeStruct::FuncName(int x, int y) -> int{ return x + y;}關(guān)鍵字auto的使用與其在自動類型推導(dǎo)代表不同的意義。
對象構(gòu)造的改良[編輯]
在標(biāo)準(zhǔn)C++中,構(gòu)造函數(shù)不能調(diào)用其它的構(gòu)造函數(shù);每個(gè)構(gòu)造函數(shù)必須自己初始化所有的成員或是調(diào)用一個(gè)共用的成員函數(shù)。基類的構(gòu)造函數(shù)不能夠直接作為派生類的構(gòu)造函數(shù);就算基類的構(gòu)造函數(shù)已經(jīng)足夠,每個(gè)派生的類別仍必須實(shí)現(xiàn)自己的構(gòu)造函數(shù)。類別中non-constant的數(shù)據(jù)成員不能夠在聲明的地方被初始化,它們只能在構(gòu)造函數(shù)中被初始化。 C++11將會提供這些問題的解決方案。
C++11允許構(gòu)造函數(shù)調(diào)用其他構(gòu)造函數(shù),這種做法稱作委托或轉(zhuǎn)接(delegation)。僅僅只需要加入少量的代碼,就能讓數(shù)個(gè)構(gòu)造函數(shù)之間達(dá)成功能復(fù)用(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中,構(gòu)造函數(shù)運(yùn)行結(jié)束代表對象構(gòu)造完成; 而允許使用轉(zhuǎn)接構(gòu)造函數(shù)的C++11則是以"任何"一個(gè)構(gòu)造函數(shù)結(jié)束代表構(gòu)造完成。使用委托的構(gòu)造函數(shù),函數(shù)本體中的代碼將于被委托的構(gòu)造函數(shù)完成后繼續(xù)運(yùn)行(如上例的PostInit())。若基類使用了委托構(gòu)造函數(shù),則派生類的構(gòu)造函數(shù)會在"所有"基底類別的構(gòu)造函數(shù)都完成后,才會開始運(yùn)行。
C++11允許派生類手動繼承基底類別的構(gòu)造函數(shù),編譯器可以使用基底類別的構(gòu)造函數(shù)完成派生類的構(gòu)造。而將基類的構(gòu)造函數(shù)帶入派生類的動作,無法選擇性地部分帶入,要不就是繼承基類全部的構(gòu)造函數(shù),要不就是一個(gè)都不繼承(不手動帶入)。此外,若牽涉到多重繼承,從多個(gè)基底類別繼承而來的構(gòu)造函數(shù)不可以有相同的函數(shù)簽名(signature)。而派生類的新加入的構(gòu)造函數(shù)也不可以和繼承而來的基底構(gòu)造函數(shù)有相同的函數(shù)簽名,因?yàn)檫@相當(dāng)于重復(fù)聲明。
語法如下:
class BaseClass{public: BaseClass(int iValue);};class DerivedClass : public BaseClass{public: using BaseClass::BaseClass;};此語法等同于DerivedClass聲明一個(gè)DerivedClass(int)的構(gòu)造函數(shù)。同時(shí)也因?yàn)镈erivedClass有了一個(gè)繼承而來的構(gòu)造函數(shù),所以不會有默認(rèn)構(gòu)造函數(shù)。
另一方面,C++11可以使用以下的語法完成成員初始化:
class SomeClass{public: SomeClass() {} explicit SomeClass(int iNewValue) : iValue(iNewValue) {}private: int iValue = 5;};若是構(gòu)造函數(shù)中沒有設(shè)置iValue的初始值,則會采用類定義中的成員初始化,令iValue初值為5。在上例中,無參數(shù)版本的構(gòu)造函數(shù),iValue便采用默認(rèn)所定義的值;而帶有一個(gè)整數(shù)參數(shù)的構(gòu)造函數(shù)則會以指定的值完成初始化。
成員初始化除了上例中的賦值形式(使用"="(,也可以采用構(gòu)造函數(shù)以及統(tǒng)一形的初始化(uniform initialization,使用"{}")。
顯式虛函數(shù)重載[編輯]
在C++里,在子類中容易意外的重載虛函數(shù)。舉例來說:
struct Base { virtual void some_func();};struct Derived : Base { void some_func();};Derived::some_func的真實(shí)意圖為何?程序員真的試圖重載該虛函數(shù),或這只是意外?這也可能是base的維護(hù)者在其中加入了一個(gè)與Derived::some_func同名且擁有相同簽名的虛函數(shù)。
另一個(gè)可能的狀況是,當(dāng)基類中的虛函數(shù)的簽名被改變,子類中擁有舊簽名的函數(shù)就不再重載該虛函數(shù)。因此,如果程序員忘記修改所有子類,運(yùn)行期將不會正確調(diào)用到該虛函數(shù)正確的實(shí)現(xiàn)。
C++11將加入支持用來防止上述情形產(chǎn)生,并在編譯期而非運(yùn)行期捕獲此類錯(cuò)誤。為保持向后兼容,此功能將是選擇性的。其語法如下:
struct Base { virtual void some_func(float);};struct Derived : Base { virtual void some_func(int) override; // 錯(cuò)誤格式:Derive::some_func並沒有override Base::some_func virtual void some_func(float) override; // OK:顯式改寫};編譯器會檢查基底類別是否存在一虛擬函數(shù),與派生類中帶有聲明override的虛擬函數(shù),有相同的函數(shù)簽名(signature);若不存在,則會回報(bào)錯(cuò)誤。
C++11也提供指示字final,用來避免類別被繼承,或是基底類別的函數(shù)被改寫:
struct Base1 final { };struct Derived1 : Base1 { }; // 錯(cuò)誤格式:class Base1已標(biāo)明為finalstruct Base2 { virtual void f() final;};struct Derived2 : Base2 { void f(); // 錯(cuò)誤格式:Base2::f已標(biāo)明為final};以上的示例中,virtual void f() final;聲明一新的虛擬函數(shù),同時(shí)也表明禁止派生函數(shù)改寫原虛擬函數(shù)。
override與final都不是語言關(guān)鍵字(keyWord),只有在特定的位置才有特別含意,其他地方仍舊可以作為一般指示字(identifier)使用。
空指針[編輯]
早在1972年,C語言誕生的初期,常數(shù)0帶有常數(shù)及空指針的雙重身份。 C使用preprocessor macro NULL表示空指針,讓NULL及0分別代表空指針及常數(shù)0。 NULL可被定義為((void*)0)或是0。
C++并不采用C的規(guī)則,不允許將void*隱式轉(zhuǎn)換為其他類型的指針。為了使代碼char* c = NULL;能通過編譯,NULL只能定義為0。這樣的決定使得函數(shù)重載無法區(qū)分代碼的語義:
void foo(char *);void foo(int);C++建議NULL應(yīng)當(dāng)定義為0,所以foo(NULL);將會調(diào)用foo(int),這并不是程序員想要的行為,也違反了代碼的直觀性。0的歧義在此處造成困擾。
C++11引入了新的關(guān)鍵字來代表空指針常數(shù):nullptr,將空指針和整數(shù)0的概念拆開。 nullptr的類型為nullptr_t,能隱式轉(zhuǎn)換為任何指針或是成員指針的類型,也能和它們進(jìn)行相等或不等的比較。而nullptr不能隱式轉(zhuǎn)換為整數(shù),也不能和整數(shù)做比較。
為了向下兼容,0仍可代表空指針常數(shù)。
char* pc = nullptr; // OKint * pi = nullptr; // OKint i = nullptr; // errorfoo(pc); // 呼叫foo(char *)強(qiáng)類型枚舉[編輯]
在標(biāo)準(zhǔn)C++中,枚舉類型不是類型安全的。枚舉類型被視為整數(shù),這使得兩種不同的枚舉類型之間可以進(jìn)行比較。C++03唯一提供的安全機(jī)制是一個(gè)整數(shù)或一個(gè)枚舉型值不能隱式轉(zhuǎn)換到另一個(gè)枚舉別型。此外,枚舉所使用整數(shù)類型及其大小都由實(shí)現(xiàn)方法定義,皆無法明確指定。最后,枚舉的名稱全數(shù)暴露于枚舉類型的作用域中,因此兩個(gè)不同的枚舉,不可以有相同的枚舉名。(好比 enum Side{ Right, Left }; 和 enum Thing{ Wrong, Right }; 不能一起使用。)
C++11引進(jìn)了一種特別的"枚舉類",可以避免上述的問題。使用enum class的語法來聲明:
enum class myEnumeration{ Val1, Val2, Val3 = 100, Val4 /* = 101 */,};此種枚舉為類型安全的。枚舉類別不能隱式地轉(zhuǎn)換為整數(shù);也無法與整數(shù)數(shù)值做比較。(表示式Enumeration::Val4 == 101會觸發(fā)編譯期錯(cuò)誤)。
枚舉類別所使用類型必須顯式指定。在上面的示例中,使用的是默認(rèn)類型int,但也可以指定其他類型:
enum class Enum2 : unsigned int {Val1, Val2};枚舉類別的作用域(scoping)不包含枚舉值的名字。使用枚舉值的名字,必須明確限定于其所屬的枚舉類型。例如,前述枚舉類別Enum2,Enum2::Val1是有意義的表示法,而單獨(dú)的Val1則否。
此外,C++11允許為傳統(tǒng)的枚舉指定使用類型:
enum Enum3 : unsigned long {Val1 = 1, Val2};枚舉名Val1定義于Enum3的枚舉范圍中(Enum3::Val1),但為了向后兼容性, Val1仍然可以于所屬枚舉類型所在的作用域中單獨(dú)使用。
在C++11中,枚舉類別的前置聲明(forward declaration)也是可行的,只要使用可指定類型的新式枚舉即可。之前的C++無法寫出枚舉的前置聲明,是由于無法確定枚舉參數(shù)所占的空間大小,C++11解決了這個(gè)問題:
enum Enum1; // C++與C++11中不合法;無法判別大小enum Enum2 : unsigned int; // 合法的C++11enum class Enum3; // 合法的C++11,列舉類別使用預(yù)設(shè)型別int enum class Enum4: unsigned int; // 合法的C++11enum Enum2 : unsigned short; // 不合法的C++11,Enum2已被聲明為unsigned int角括號[編輯]
標(biāo)準(zhǔn)C++的剖析器一律將">>"視為右移運(yùn)算符。但在嵌套模板定義式中,絕大多數(shù)的場合其實(shí)都代表兩個(gè)連續(xù)右角括號。為了避免剖析器誤判,撰碼時(shí)不能把右角括號連著寫。
C++11變更了剖析器的解讀規(guī)則;當(dāng)遇到連續(xù)的右角括號時(shí),會在合理的情況下將右尖括號解析為模板引用的結(jié)束符號。給使用>,>=,>>的表達(dá)式加上圓括號,可以避免其與圓括號外部的左尖括號相匹配:
template<bool bTest> SomeType;std::vector<SomeType<1>2>> x1; // 解讀為std::vector of "SomeType<true> 2>", // 非法的表示式,整數(shù)1被轉(zhuǎn)換為bool型別truestd::vector<SomeType<(1>2)>> x1; // 解讀為std::vector of "SomeType<false>", // 合法的C++11表示式,(1>2)被轉(zhuǎn)換為bool型別false顯式類型轉(zhuǎn)換子[編輯]
C++引入了關(guān)鍵字explicit來避免用戶自定的單引用構(gòu)造函數(shù)被當(dāng)成隱式類型轉(zhuǎn)換子。但是,卻沒有限制明確定義的類型轉(zhuǎn)換函數(shù)。比方說,一個(gè)smart pointer類別具有一個(gè)operator bool(),被定義成若該smart pointer不為null則傳回true,反之傳回false。遇到這樣的代碼時(shí):if(smart_ptr_variable),編譯器可以借由operator bool()隱式轉(zhuǎn)換成布爾值,和測試原生指針的方法一樣。但是這類隱式轉(zhuǎn)換同樣也會發(fā)生在非預(yù)期之處。由于C++的bool類型也是算術(shù)類型,能隱式換為整數(shù)甚至是浮點(diǎn)數(shù)。拿對象轉(zhuǎn)換出的布爾值做布爾運(yùn)算以外的數(shù)學(xué)運(yùn)算,往往不是程序員想要的。
在C++11中,關(guān)鍵字explicit修飾符也能套用到類型轉(zhuǎn)換函數(shù)上。如同構(gòu)造函數(shù)一樣,它能避免類型轉(zhuǎn)換函數(shù)被隱式轉(zhuǎn)換調(diào)用。但C++11特別指定,在if條件式、循環(huán)、邏輯運(yùn)算等需要布爾值的地方,將其作為顯式類型轉(zhuǎn)換,因此即使對應(yīng)的類型轉(zhuǎn)換函數(shù)被explicit修飾也可以調(diào)用。這主要為了解決safe bool問題。
模板的別名[編輯]
在進(jìn)入這個(gè)主題之前,各位應(yīng)該先弄清楚“模板”和“類型”本質(zhì)上的不同。class template (類別模板,是模板)是用來產(chǎn)生template class(模板類別,是類型)。在傳統(tǒng)的C++標(biāo)準(zhǔn),typedef可定義模板類別一個(gè)新的類型名稱,但是不能夠使用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); // 傳統(tǒng)語法using PFD = void (*)(double); // 新增語法無限制的unions[編輯]
在C++03中,并非任意的類型都能做為union的成員。比方說,帶有non-trivial 構(gòu)造函數(shù)的類型就不能是union的成員。在新的標(biāo)準(zhǔn)里,移除了所有對union的使用限制,除了其成員仍然不能是引用類型。這一改變使得union更強(qiáng)大,更有用,也易于使用。[2]
但是如果union成員具有非平凡的特殊成員函數(shù),則編譯器不會為union生成對應(yīng)的特殊成員函數(shù),必須手工定義。
以下為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建構(gòu)式 // 合法的C++11 u1(int x, int y):p(x,y) {}; //Visual Studio 2015編譯通過};這一改變僅放寬union的使用限制,不會影響既有的舊代碼。
核心語言能力的提升[編輯]
這些特性讓C++語言能夠做一些以前做不到的,或者極其復(fù)雜的,或者需求一些不可移植的庫的事情。
可變參數(shù)模板[編輯]
在C++11之前,不論是類模板或是函數(shù)模板,都只能按其被聲明時(shí)所指定的樣子,接受一組固定數(shù)目的模板參數(shù);C++11加入新的表示法,允許任意個(gè)數(shù)、任意類別的模板參數(shù),不必在定義時(shí)將參數(shù)的個(gè)數(shù)固定。
template<typename... Values> class tuple;模板類tuple的對象,能接受不限個(gè)數(shù)的typename作為它的模板形參:
class tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> someInstanceName;實(shí)參的個(gè)數(shù)也可以是0,所以class tuple<> someInstanceName這樣的定義也是可以的。
若不希望產(chǎn)生實(shí)參個(gè)數(shù)為0的不定長參數(shù)模板,則可以采用以下的定義:
template<typename First, typename... Rest> class tuple;不定長參數(shù)模板也能運(yùn)用到模板函數(shù)上。傳統(tǒng)C中的printf函數(shù),雖然也能達(dá)成不定個(gè)數(shù)的形參的調(diào)用,但其并非類別安全。以下的樣例中,C++11除了能定義類別安全的變長參數(shù)函數(shù)外,還能讓類似printf的函數(shù)能自然地處理非內(nèi)置類別的對象。除了在模板參數(shù)中能使用...表示不定長模板參數(shù)外,函數(shù)參數(shù)也使用同樣的表示法代表不定長參數(shù)。
template<typename... Params> void printf(const std::string &strFormat, Params... parameters);其中,Params與parameters分別代表模板與函數(shù)的變長參數(shù)集合,稱之為參數(shù)包(parameter pack)。參數(shù)包必須要和運(yùn)算符"..."搭配使用,避免語法上的歧義。
變長參數(shù)模板中,變長參數(shù)包無法如同一般參數(shù)在類或函數(shù)中使用; 因此典型的手法是以遞歸的方法取出可用參數(shù),參看以下的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...); // 即便當(dāng)*s == 0也會產(chǎn)生調(diào)用,以檢測更多的類型參數(shù)。 return; } std::cout << *s++; } throw std::logic_error("extra arguments provided to printf");}printf會不斷地遞歸調(diào)用自身:函數(shù)參數(shù)包args...在調(diào)用時(shí),會被模板類別匹配分離為T value和Args... args。直到args...變?yōu)榭諈?shù),則會與簡單的printf(const char *s)形成匹配,結(jié)束遞歸。
另一個(gè)例子為計(jì)算模板參數(shù)的個(gè)數(shù),這里使用相似的技巧展開模板參數(shù)包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;};雖然沒有一個(gè)簡潔的機(jī)制能夠?qū)ψ冮L參數(shù)模板中的值進(jìn)行迭代,但使用運(yùn)算符"..."還能在代碼各處對參數(shù)包施以更復(fù)雜的展開操作。舉例來說,一個(gè)模板類的定義:
template <typename... BaseClasses> class ClassName : public BaseClasses...{public: ClassName (BaseClasses&&... baseClasses) : BaseClasses(baseClasses)... {}}BaseClasses...會被展開成類別ClassName的基底類;ClassName的構(gòu)造函數(shù)需要所有基類的右值引用,而每一個(gè)基類都是以傳入的參數(shù)做初始化(BaseClasses(baseClasses)...)。
在函數(shù)模板中,變長參數(shù)可以和右值引用搭配,達(dá)成形參的完美轉(zhuǎn)送(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)...)); }}參數(shù)包parms可展開為TypeToConstruct構(gòu)造函數(shù)的形參。表達(dá)式std::forward<Args>(params)可將形參的類別信息保留(利用右值引用),傳入構(gòu)造函數(shù)。而運(yùn)算符"..."則能將前述的表達(dá)式套用到每一個(gè)參數(shù)包中的參數(shù)。這種工廠函數(shù)(factory function)的手法,使用std::shared_ptr管理配置對象的內(nèi)存,避免了不當(dāng)使用所產(chǎn)生的內(nèi)存泄漏(memory leaks)。
此外,變長參數(shù)的數(shù)量可以藉以下的語法得知:
template<typename ...Args> struct SomeStruct{ static const int size = sizeof...(Args);}SomeStruct<Type1, Type2>::size是2,而SomeStruct<>::size會是0。(sizeof...(Args)的結(jié)果是編譯期常數(shù)。)
新的字符串字面值[編輯]
標(biāo)準(zhǔn)C++提供了兩種字符串字面值。第一種,包含有雙引號,產(chǎn)生以空字符結(jié)尾的const char數(shù)組。第二種有著前標(biāo)L,產(chǎn)生以空字符結(jié)尾的const wchar_t數(shù)組,其中wchar_t代表寬字符。對于Unicode編碼的支持尚付闕如。
為了加強(qiáng)C++編譯器對Unicode的支持,類別char的定義被修改為其大小至少能夠存儲UTF-8的8位編碼,并且能夠容納編譯器的基本字符集的任何成員。
C++11將支持三種Unicode編碼方式:UTF-8,UTF-16,和UTF-32。除了上述char定義的變更,C++11將增加兩種新的字符類別:char16_t和char32_t。它們各自被設(shè)計(jì)用來存儲UTF-16以及UTF-32的字符。
以下展示如何產(chǎn)生使用這些編碼的字符串字面值:
u8"I'm a UTF-8 string."u"This is a UTF-16 string."U"This is a UTF-32 string."第一個(gè)字符串的類別是通常的const char[];第二個(gè)字符串的類別是const char16_t[];第三個(gè)字符串的類別是const char32_t[]。
當(dāng)創(chuàng)建Unicode字符串字面值時(shí),可以直接在字符串內(nèi)插入U(xiǎn)nicode 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個(gè)比特的十六進(jìn)制數(shù)值;它不需要'0x'的前標(biāo)。識別字'/u'代表了一個(gè)16位的Unicode codepoint;如果要輸入32位的codepoint,使用'/U'和32個(gè)比特的十六進(jìn)制數(shù)值。只有有效的Unicode codepoints能夠被輸入。舉例而言,codepoints在范圍U+D800—U+DFFF之間是被禁止的,它們被保留給UTF-16編碼的surrogate pairs。
有時(shí)候避免手動將字符串換碼也是很有用的,特別是在使用xml文件或是一些腳本語言的字面值的時(shí)候。C++11將提供raw(原始)字符串字面值:
R"(The String Data / Stuff " )"R"delimiter(The String Data / Stuff " )delimiter"在第一個(gè)例子中,任何包含在( )括號(標(biāo)準(zhǔn)已經(jīng)從[]改為())當(dāng)中的都是字符串的一部分。其中"和/字符不需要經(jīng)過轉(zhuǎn)義。在第二個(gè)例子中,"delimiter(開始字符串,只有在遇到)delimiter"才代表結(jié)束。其中delimiter可以是最多16個(gè)字符的任意的字符串(包含空字符串),但不能包含空格、控制字符和'('、')'、'/'。原始字符串允許用戶使用圓括號(,),例如R"delimiter((a-z))delimiter"等價(jià)于"(a-z)"。原始字符串字面值能夠和寬字面值或是Unicode字面值結(jié)合:
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.)"用戶定義字面量[編輯]
標(biāo)準(zhǔn)C++提供了數(shù)種字面值。字符"12.5"是能夠被編譯器解釋為數(shù)值12.5的double類別字面值。然而,加上"f"的后置,像是"12.5f",則會產(chǎn)生數(shù)值為12.5的float類別字面值。之前的C++規(guī)范中字面值的修飾符是固定的,C++代碼不能創(chuàng)立新的字面修飾符。
C++11開放用戶定義新的字面修飾符(literal modifier),利用自定義的修飾符完成由字面值構(gòu)造對象。
字面值轉(zhuǎn)換可以定義為兩個(gè)階段:原始與轉(zhuǎn)換后(raw與cooked)。原始字面值指特定類型的字符序列,而轉(zhuǎn)換后的字面值則代表另一種類別。如字面值1234,原始字面值是'1', '2', '3', '4'的字符序列;而轉(zhuǎn)換后的字面值是整數(shù)值1234。另外,字面值0xA轉(zhuǎn)換前是序列'0', 'x', 'A';轉(zhuǎn)換后代表整數(shù)值10。
多任務(wù)內(nèi)存模型[編輯]
參見:內(nèi)存模型(computing)C++標(biāo)準(zhǔn)委員會計(jì)劃統(tǒng)一對多線程編程的支持。
這將涉及兩個(gè)部分:第一、設(shè)計(jì)一個(gè)可以使多個(gè)線程在一個(gè)進(jìn)程中共存的內(nèi)存模型;第二、為線程之間的交互提供支持。第二部分將由程序庫提供支持,更多請看線程支持。
在多個(gè)線程可能會訪問相同內(nèi)存的情形下,由一個(gè)內(nèi)存模型對它們進(jìn)行調(diào)度是非常有必要的。遵守模型規(guī)則的程序是被保證正確運(yùn)行的,但違反規(guī)則的程序會發(fā)生不可預(yù)料的行為,這些行為依賴于編譯器的最優(yōu)化和內(nèi)存一致性的問題。
thread-local的存儲期限[編輯]
在多線程環(huán)境下,讓各線程擁有各自的參數(shù)是很普遍的。這已經(jīng)存在于函數(shù)的區(qū)域參數(shù),但是對于全域和靜態(tài)參數(shù)都還不行。
新的thread_local存儲期限(在現(xiàn)行的static、dynamic和automatic之外)被作為下個(gè)標(biāo)準(zhǔn)而提出。線程區(qū)域的存儲期限會借由存儲指定字thread_local來表明。
static對象(生命周期為整個(gè)程序的運(yùn)行期間)的存儲期限可以被thread-local給替代。就如同其他使用static存儲期的參數(shù),thread-local對象能夠以構(gòu)造函數(shù)初始化并以析構(gòu)函數(shù)摧毀。
使用或禁用對象的默認(rèn)函數(shù)[編輯]
在傳統(tǒng)C++中,若用戶沒有提供,則編譯器會自動為對象生成默認(rèn)構(gòu)造函數(shù)(default constructor)、復(fù)制構(gòu)造函數(shù)(copy constructor),賦值運(yùn)算符(copy assignment operator operator=)以及析構(gòu)函數(shù)(destructor)。另外,C++也為所有的類定義了數(shù)個(gè)全域運(yùn)算符(如operator delete及operator new)。當(dāng)用戶有需要時(shí),也可以提供自定義的版本改寫上述的函數(shù)。
問題在于原先的c++無法精確地控制這些默認(rèn)函數(shù)的生成。比方說,要讓類別不能被拷貝,必須將復(fù)制構(gòu)造函數(shù)與賦值運(yùn)算符聲明為private,并不去定義它們。嘗試使用這些未定義的函數(shù)會導(dǎo)致編譯期或鏈接期的錯(cuò)誤。但這種手法并不是一個(gè)理想的解決方案。
此外,編譯器產(chǎn)生的默認(rèn)構(gòu)造函數(shù)與用戶定義的構(gòu)造函數(shù)無法同時(shí)存在。若用戶定義了任何構(gòu)造函數(shù),編譯器便不會生成默認(rèn)構(gòu)造函數(shù); 但有時(shí)同時(shí)帶有上述兩者提供的構(gòu)造函數(shù)也是很有用的。目前并沒有顯式指定編譯器產(chǎn)生默認(rèn)構(gòu)造函數(shù)的方法。
C++11允許顯式地表明采用或拒用編譯器提供的內(nèi)置函數(shù)。例如要求類別帶有默認(rèn)構(gòu)造函數(shù),可以用以下的語法:
struct SomeType{ SomeType() = default; // 預(yù)設(shè)建構(gòu)式的顯式聲明 SomeType(OtherType value);};另一方面,也可以禁止編譯器自動產(chǎn)生某些函數(shù)。如下面的例子,類別不可復(fù)制:
struct NonCopyable{ NonCopyable & operator=(const NonCopyable&) = delete; NonCopyable(const NonCopyable&) = delete; NonCopyable() = default;};禁止類別以operator new配置內(nèi)存:
struct NonNewable{ void *operator new(std::size_t) = delete;};此種對象只能生成于stack中或是當(dāng)作其他類別的成員,它無法直接配置于heap之中,除非使用了與平臺相關(guān),不可移植的手法。(使用placement new運(yùn)算符雖然可以在用戶自配置的內(nèi)存上調(diào)用對象構(gòu)造函數(shù),但在此例中其他形式的new運(yùn)算符一并被上述的定義屏蔽("name hiding"),所以也不可行。)
= delete的聲明(同時(shí)也是定義)也能適用于非內(nèi)置函數(shù),禁止成員函數(shù)以特定的形參調(diào)用:
struct NoDouble{ void f(int i); void f(double) = delete;};若嘗試以double的形參調(diào)用f(),將會引發(fā)編譯期錯(cuò)誤,編譯器不會自動將double形參轉(zhuǎn)型為int再調(diào)用f()。若要徹底的禁止以非int的形參調(diào)用f(),可以將= delete與模板相結(jié)合:
struct OnlyInt{ void f(int i); template<class T> void f(T) = delete;};long long int類別[編輯]
在32位系統(tǒng)上,一個(gè)long long int是保有至少64個(gè)有效比特的整數(shù)類別。C99將這個(gè)類別引入了標(biāo)準(zhǔn)C中,目前大多數(shù)的C++編譯器也支持這種類別。C++11將把這種類別添加到標(biāo)準(zhǔn)C++中。
靜態(tài)assertion[編輯]
C++提供了兩種方法測試assertion(聲明):宏assert以及預(yù)處理器指令#error。但是這兩者對于模版來說都不合用。宏在運(yùn)行期測試assertion,而預(yù)處理器指令則在前置處理時(shí)測試assertion,這時(shí)候模版還未能實(shí)例化。所以它們都不適合來測試牽扯到模板參數(shù)的相關(guān)特性。
新的機(jī)能會引進(jìn)新的方式可以在編譯期測試assertion,只要使用新的關(guān)鍵字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!" ) ;} ;當(dāng)常數(shù)表達(dá)式值為false時(shí),編譯器會產(chǎn)生相應(yīng)的錯(cuò)誤消息。第一個(gè)例子是預(yù)處理器指令#error的替代方案;第二個(gè)例子會在每個(gè)模板類別Check生成時(shí)檢查assertion。
靜態(tài)assertion在模板之外也是相當(dāng)有用的。例如,某個(gè)算法的實(shí)現(xiàn)依賴于long long類別的大小比int還大,這是標(biāo)準(zhǔn)所不保證的。這種假設(shè)在大多數(shù)的系統(tǒng)以及編譯器上是有效的,但不是全部。
允許sizeof運(yùn)算符作用在類別的數(shù)據(jù)成員上,無須明確的對象[編輯]
在標(biāo)準(zhǔn)C++,sizeof可以作用在對象以及類別上。但是不能夠做以下的事:
struct SomeType { OtherType member; };sizeof(SomeType::member); // 直接由SomeType型別取得非靜態(tài)成員的大小,C++03不行。C++11允許這會傳回OtherType的大小。C++03并不允許這樣做,所以會引發(fā)編譯錯(cuò)誤。C++11將會允許這種使用。
垃圾回收機(jī)制[編輯]
是否會自動回收那些無法被使用到(unreachable)的動態(tài)分配對象由實(shí)現(xiàn)決定。
C++標(biāo)準(zhǔn)程序庫的變更[編輯]
C++11標(biāo)準(zhǔn)程序庫有數(shù)個(gè)新機(jī)能。其中許多可以在現(xiàn)行標(biāo)準(zhǔn)下實(shí)現(xiàn),而另外一些則依賴于(或多或少)新的C++11核心語言機(jī)能。
新的程序庫的大部分被定義于C++標(biāo)準(zhǔn)委員會的Library Technical Report(稱TR1),于2005年發(fā)布。各式TR1的完全或部分實(shí)現(xiàn)目前提供在名字空間std::tr1。C++11會將其移置于名字空間std之下。
標(biāo)準(zhǔn)庫組件上的升級[編輯]
目前的標(biāo)準(zhǔn)庫能受益于C++11新增的一些語言特性。舉例來說,對于大部分的標(biāo)準(zhǔn)庫容器而言,像是搬移內(nèi)含大量元素的容器,或是容器之內(nèi)對元素的搬移,基于右值引用(Rvalue reference)的move構(gòu)造函數(shù)都能優(yōu)化前述動作。在適當(dāng)?shù)那闆r下,標(biāo)準(zhǔn)庫組件將可利用C++11的語言特性進(jìn)行升級。這些語言特性包含但不局限以下所列:
右值引用和其相關(guān)的move支持支持UTF-16編碼,和UTF-32字符集變長參數(shù)模板(與右值引用搭配可以達(dá)成完美轉(zhuǎn)發(fā)(perfect forwarding))編譯期常數(shù)表達(dá)式Decltype顯式類別轉(zhuǎn)換子使用或禁用對象的默認(rèn)函數(shù)此外,自C++標(biāo)準(zhǔn)化之后已經(jīng)過許多年。現(xiàn)有許多代碼利用到了標(biāo)準(zhǔn)庫;這同時(shí)揭露了部分的標(biāo)準(zhǔn)庫可以做些改良。其中之一是標(biāo)準(zhǔn)庫的內(nèi)存配置器(allocator)。C++11將會加入一個(gè)基于作用域模型的內(nèi)存配置器來支持現(xiàn)有的模型。
線程支持[編輯]
雖然C++11會在語言的定義上提供一個(gè)內(nèi)存模型以支持線程,但線程的使用主要將以C++11標(biāo)準(zhǔn)庫的方式呈現(xiàn)。
C++11標(biāo)準(zhǔn)庫會提供類別thread(std::thread)。若要運(yùn)行一個(gè)線程,可以創(chuàng)建一個(gè)類別thread的實(shí)體,其初始參數(shù)為一個(gè)函數(shù)對象,以及該函數(shù)對象所需要的參數(shù)。通過成員函數(shù)std::thread::join()對線程會合的支持,一個(gè)線程可以暫停直到其它線程運(yùn)行完畢。若有底層平臺支持,成員函數(shù)std::thread::native_handle()將可提供對原生線程對象運(yùn)行平臺特定的操作。
對于線程間的同步,標(biāo)準(zhǔn)庫將會提供適當(dāng)?shù)幕コ怄i(像是std::mutex,std::recursive_mutex等等)和條件參數(shù)(std::condition_variable和std::condition_variable_any)。前述同步機(jī)制將會以RAII鎖(std::lock_guard和std::unique_lock)和鎖相關(guān)算法的方式呈現(xiàn),以方便程序員使用。
對于要求高性能,或是極底層的工作,有時(shí)或甚至是必須的,我們希望線程間的通信能避免互斥鎖使用上的開銷。以原子操作來訪問內(nèi)存可以達(dá)成此目的。針對不同情況,我們可以通過顯性的內(nèi)存屏障改變該訪問內(nèi)存動作的可見性。
對于線程間異步的傳輸,C++11標(biāo)準(zhǔn)庫加入了以及std::packaged_task用來包裝一個(gè)會傳回異步結(jié)果的函數(shù)調(diào)用。因?yàn)槿鄙俳Y(jié)合數(shù)個(gè)future的功能,和無法判定一組promise集合中的某一個(gè)promise是否完成,futures此一提案因此而受到了批評。
更高級的線程支持,如線程池,已經(jīng)決定留待在未來的Technical Report加入此類支持。更高級的線程支持不會是C++11的一部分,但設(shè)想是其最終實(shí)現(xiàn)將創(chuàng)建在目前已有的線程支持之上。
std::async提供了一個(gè)簡便方法以用來運(yùn)行線程,并將線程綁定在std::future。用戶可以選擇一個(gè)工作是要多個(gè)線程上異步的運(yùn)行,或是在一個(gè)線程上運(yùn)行并等待其所需要的數(shù)據(jù)。默認(rèn)的情況,實(shí)現(xiàn)可以根據(jù)底層硬件選擇前面兩個(gè)選項(xiàng)的其中之一。另外在較簡單的使用情形下,實(shí)現(xiàn)也可以利用線程池提供支持。
多元組類別[編輯]
多元組是一個(gè)內(nèi)由數(shù)個(gè)異質(zhì)對象以特定順序排列而成的數(shù)據(jù)結(jié)構(gòu)。多元組可被視為是struct其數(shù)據(jù)成員的一般化。
由TR1演進(jìn)而來的C++11多元組類別將受益于C++11某些特色像是可變參數(shù)模板。TR1版本的多元組類別對所能容納的對象個(gè)數(shù)會因?qū)崿F(xiàn)而有所限制,且實(shí)現(xiàn)上需要用到大量的宏技巧。相反的,C++11版本的多元組型基本上于對其能容納的對象個(gè)數(shù)沒有限制。然而,編譯器對于模板實(shí)體化的遞歸深度上的限制仍舊影響了元組類別所能容納的對象個(gè)數(shù)(這是無法避免的情況);C++11版本的多元組型不會把這個(gè)值讓用戶知道。
使用可變參數(shù)模板,多元組類別的聲明可以長得像下面這樣:
template <class ...Types> class tuple;底下是一個(gè)多元組類別的定義和使用情況:
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的第一個(gè)元素賦值給lengthy(索引從零開始起跳)std::get<3>(proof) = " Beautiful!"; // 修改proof的第四個(gè)元素我們可以定義一個(gè)多元組類別對象proof而不指定其內(nèi)容,前提是proof里的元素其類別定義了默認(rèn)構(gòu)造函數(shù)(default constructor)。此外,以一個(gè)多元組類別對象賦值給另一個(gè)多元組類別對象是可能的,但只有在以下情況:若這兩個(gè)多元組類別相同,則其內(nèi)含的每一個(gè)元素其類別都要定義拷貝構(gòu)造函數(shù)(copy constructor);否則的話,賦值操作符右邊的多元組其內(nèi)含元素的類別必須能轉(zhuǎn)換成左邊的多元組其對應(yīng)的元素類別,又或者賦值操作符左邊的多元組其內(nèi)含元素的類別必須定義適當(dāng)?shù)臉?gòu)造函數(shù)。
std::tuple< int , double, string > t1;std::tuple< char, short , const char * > t2 ('X', 2, "Hola!");t1 = t2 ; // 可行。前兩個(gè)元素會作型別轉(zhuǎn)換, // 第三個(gè)字串元素可由'const char *'所建構(gòu)。多元組類型對象的比較運(yùn)算是可行的(當(dāng)它們擁有同樣數(shù)量的元素)。此外,C++11提供兩個(gè)表達(dá)式用來檢查多元組類型的一些特性(僅在編譯期做此檢查)。
std::tuple_size<T>::value回傳多元組T內(nèi)的元素個(gè)數(shù),std::tuple_element<I, T>::type回傳多元組T內(nèi)的第I個(gè)元素的類別散列表[編輯]
在過去,不斷有要求想將散列表(無序關(guān)系式容器)引進(jìn)標(biāo)準(zhǔn)庫。只因?yàn)闀r(shí)間上的限制,散列表才沒有被標(biāo)準(zhǔn)庫所采納。雖然,散列表在最糟情況下(如果出現(xiàn)許多沖突(collision)的話)在性能上比不過平衡樹。但實(shí)際運(yùn)用上,散列表的表現(xiàn)則較佳。
因?yàn)闃?biāo)準(zhǔn)委員會還看不到有任何機(jī)會能將開放定址法標(biāo)準(zhǔn)化,所以目前沖突僅能通過鏈地址法(linear chaining)的方式處理。為避免與第三方庫發(fā)展的散列表發(fā)生名稱上的沖突,前綴將采用unordered而非hash。
庫將引進(jìn)四種散列表,其中差別在于底下兩個(gè)特性:是否接受具相同鍵值的項(xiàng)目(Equivalent keys),以及是否會將鍵值映射到相對應(yīng)的數(shù)據(jù)(Associated values)。
| 散列表類型 | 有無關(guān)系值 | 接受相同鍵值 |
|---|---|---|
std::unordered_set | 否 | 否 |
std::unordered_multiset | 否 | 是 |
std::unordered_map | 是 | 否 |
std::unordered_multimap | 是 | 是 |
上述的類別將滿足對一個(gè)容器類別的要求,同時(shí)也提供訪問其中元素的成員函數(shù):insert,erase,begin,end。
散列表不需要對現(xiàn)有核心語言做擴(kuò)展(雖然散列表的實(shí)現(xiàn)會利用到C++11新的語言特性),只會對頭文件<functional>做些許擴(kuò)展,并引入<unordered_set>和<unordered_map>兩個(gè)頭文件。對于其它現(xiàn)有的類別不會有任何修改。同時(shí),散列表也不會依賴其它標(biāo)準(zhǔn)庫的擴(kuò)展功能。
過去許多或多或少標(biāo)準(zhǔn)化的程序庫被創(chuàng)建用來處理正則表達(dá)式。有鑒于這些算法的使用非常普遍,因此標(biāo)準(zhǔn)程序庫將會包含他們,并使用各種面向?qū)ο笳Z言的潛力。
這個(gè)新的程序庫,被定義于<regex>頭文件,由幾個(gè)新的類別所組成:
basic_regex的實(shí)體表示模式匹配的情況以模板類match_results的實(shí)體表示函數(shù)regex_search是用來搜索模式;若要搜索并替換,則要使用函數(shù)regex_replace,該函數(shù)會回傳一個(gè)新的字符串。算法regex_search和regex_replace接受一個(gè)正則表達(dá)式(模式)和一個(gè)字符串,并將該模式匹配的情況存儲在struct match_results。
底下描述了match_results的使用情況:
const char *reg_esp = "[ ,.//t//n;:]" ; // 分隔字元列表std::regex rgx(reg_esp) ; // 'regex'是樣板類'basic_regex'以型別為'char' // 的參數(shù)具現(xiàn)化的實(shí)體std::cmatch match ; // 'cmatch'是樣板類match_results'以型別為'const char *' // '的參數(shù)具現(xiàn)化的實(shí)體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" ; }}注意雙反斜線的使用,因?yàn)镃++將反斜線作為轉(zhuǎn)義字符使用。但C++11的raw string可以用來避免此一問題。庫<regex>不需要改動到現(xiàn)有的頭文件,同時(shí)也不需要對現(xiàn)有的語言作擴(kuò)展。
通用智能指針[編輯]
這些指針是由TR1智能指針演變而來。注意! 智能指針是類別而非一般指針。
shared_ptr是一引用計(jì)數(shù)(reference-counted)指針,其行為與一般C++指針極為相似。在TR1的實(shí)現(xiàn)中,缺少了一些一般指針?biāo)鶕碛械奶厣袷莿e名或是指針運(yùn)算。C++11新增前述特色。
一個(gè)shared_ptr只有在已經(jīng)沒有任何其它shared_ptr指向其原本所指向?qū)ο髸r(shí),才會銷毀該對象。
一個(gè)weak_ptr指向的是一個(gè)被shared_ptr所指向的對象。該weak_ptr可以用來決定該對象是否已被銷毀。weak_ptr不能被解引用;想要訪問其內(nèi)部所保存的指針,只能通過shared_ptr。有兩種方法可達(dá)成此目的。第一,類別shared_ptr有一個(gè)以weak_ptr為參數(shù)的構(gòu)造函數(shù)。第二,類別weak_ptr有一個(gè)名為lock的成員函數(shù),其回返值為一個(gè)shared_ptr。weak_ptr并不擁有它所指向的對象,因此不影響該對象的銷毀與否。
底下是一個(gè)shared_ptr的使用樣例:
int main( ){ std::shared_ptr<double> p_first(new double) ; { std::shared_ptr<double> p_copy = p_first ; *p_copy = 21.2; } // 此時(shí)'p_copy'會被銷毀,但動態(tài)分配的double不會被銷毀。 return 0; // 此時(shí)'p_first'會被銷毀,動態(tài)分配的double也會被銷毀(因?yàn)椴辉儆兄羔樦赶蛩auto_ptr將會被C++標(biāo)準(zhǔn)所廢棄,取而代之的是unique_ptr。unique_ptr提供auto_ptr大部分特性,唯一的例外是auto_ptr的不安全、隱性的左值搬移。不像auto_ptr,unique_ptr可以存放在C++11提出的那些能察覺搬移動作的容器之中。
可擴(kuò)展的隨機(jī)數(shù)功能[編輯]
C標(biāo)準(zhǔn)庫允許使用rand函數(shù)來生成偽隨機(jī)數(shù)。不過其算法則取決于各程序庫開發(fā)者。C++直接從C繼承了這部分,但是C++11將會提供產(chǎn)生偽隨機(jī)數(shù)的新方法。
C++11的隨機(jī)數(shù)功能分為兩部分:第一,一個(gè)隨機(jī)數(shù)生成引擎,其中包含該生成引擎的狀態(tài),用來產(chǎn)生隨機(jī)數(shù)。第二,一個(gè)分布,這可以用來決定產(chǎn)生隨機(jī)數(shù)的范圍,也可以決定以何種分布方式產(chǎn)生隨機(jī)數(shù)。隨機(jī)數(shù)生成對象即是由隨機(jī)數(shù)生成引擎和分布所構(gòu)成。
不同于C標(biāo)準(zhǔn)庫的rand;針對產(chǎn)生隨機(jī)數(shù)的機(jī)制,C++11將會提供三種算法,每一種算法都有其強(qiáng)項(xiàng)和弱項(xiàng):
| 模板類 | 整數(shù)/浮點(diǎn)數(shù) | 質(zhì)量 | 速度 | 狀態(tài)數(shù)* |
|---|---|---|---|---|
| linear_congruential | 整數(shù) | 低 | 中等[來源請求] | 1 |
| subtract_with_carry | 兩者皆可 | 中等 | 快 | 25 |
| mersenne_twister | 整數(shù) | 佳 | 快 | 624 |
C++11將會提供一些標(biāo)準(zhǔn)分布:uniform_int_distribution(離散型均勻分布),bernoulli_distribution(伯努利分布),geometric_distribution(幾何分布),poisson_distribution(卜瓦松分布),binomial_distribution(二項(xiàng)分布),uniform_real_distribution(離散型均勻分布),exponential_distribution(指數(shù)分布),normal_distribution(正態(tài)分布)和gamma_distribution(伽瑪分布)。
底下描述一個(gè)隨機(jī)數(shù)生成對象如何由隨機(jī)數(shù)生成引擎和分布構(gòu)成:
std::uniform_int_distribution<int> distribution(0, 99); // 以離散型均勻分佈方式產(chǎn)生int亂數(shù),範(fàn)圍落在0到99之間std::mt19937 engine; // 建立亂數(shù)生成引擎auto generator = std::bind(distribution, engine); // 利用bind將亂數(shù)生成引擎和分布組合成一個(gè)亂數(shù)生成物件int random = generator(); // 產(chǎn)生亂數(shù)包裝引用[編輯]
我們可以通過實(shí)體化模板類
reference_wrapper得到一個(gè)包裝引用(wrapper reference)。包裝引用類似于一般的引用。對于任意對象,我們可以通過模板類ref得到一個(gè)包裝引用(至于constant reference則可通過cref得到)。當(dāng)模板函數(shù)需要形參的引用而非其拷貝,這時(shí)包裝引用就能派上用場:
// 此函數(shù)將得到形參'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) ; // 實(shí)體化'g<void (int &r), int>' // 'i'不會被修改 std::cout << i << std::endl; // 輸出0 g (f, std::ref(i)); // 實(shí)體化'g<void(int &r),reference_wrapper<int>>' // 'i'會被修改 std::cout << i << std::endl; // 輸出1}這項(xiàng)功能將加入頭文件
<utility>之中,而非通過擴(kuò)展語言來得到這項(xiàng)功能。多態(tài)函數(shù)對象包裝器[編輯]
針對函數(shù)對象的多態(tài)包裝器(又稱多態(tài)函數(shù)對象包裝器)在語義和語法上和函數(shù)指針相似,但不像函數(shù)指針那么狹隘。只要能被調(diào)用,且其參數(shù)能與包裝器兼容的都能以多態(tài)函數(shù)對象包裝器稱之(函數(shù)指針,成員函數(shù)指針或仿函數(shù))。
通過以下例子,我們可以了解多態(tài)函數(shù)對象包裝器的特性:
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) { // 因?yàn)樯形促x值與'func2'任何函式,此條件式為真 bool adjacent(long x, long y); func2 = &adjacent ; // 可行。'adjacent'的型參和回返值型別可透過型別轉(zhuǎn)換進(jìn)而與'func2'相符 struct Test { bool operator()(short x, short y); }; Test car; func = std::ref(car); // 樣板類'std::ref'回傳一個(gè)struct 'car' // 其成員函式'operator()'的包裝}func = func2; // 可行。'func2'的型參和回返值型別可透過型別轉(zhuǎn)換進(jìn)而與'func'相符模板類
function將定義在頭文件<functional>,而不須更動到語言本身。用于元編程的類別屬性[編輯]
對于那些能自行創(chuàng)建或修改本身或其它程序的程序,我們稱之為元編程。這種行為可以發(fā)生在編譯或運(yùn)行期。C++標(biāo)準(zhǔn)委員會已經(jīng)決定引進(jìn)一組由模板實(shí)現(xiàn)的庫,程序員可利用此一庫于編譯期進(jìn)行元編程。
底下是一個(gè)以元編程來計(jì)算指數(shù)的例子:
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;許多算法能作用在不同的數(shù)據(jù)類別;C++模板支持泛型,這使得代碼能更緊湊和有用。然而,算法經(jīng)常會需要目前作用的數(shù)據(jù)類別的信息。這種信息可以通過類別屬性(
type traits)于模板實(shí)體化時(shí)將該信息萃取出來。類別屬性能識別一個(gè)對象的種類和有關(guān)一個(gè)類別(class或struct)的特征。頭文件
<type_traits>描述了我們能識別那些特征。底下的例子說明了模板函數(shù)‘elaborate’是如何根據(jù)給定的數(shù)據(jù)類別,從而實(shí)體化某一特定的算法(
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) { /*...*/ }};// 根據(jù)給定的型別,實(shí)體化之後的'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 ) ;}此種編程技巧能寫出優(yōu)美、簡潔的代碼;然而除錯(cuò)是此種編程技巧的弱處:編譯期的錯(cuò)誤消息讓人不知所云,運(yùn)行期的除錯(cuò)更是困難。
用于計(jì)算函數(shù)對象回返類型的統(tǒng)一方法[編輯]
要在編譯期決定一個(gè)模板仿函數(shù)的回返值類別并不容易,特別是當(dāng)回返值依賴于函數(shù)的參數(shù)時(shí)。舉例來說:
struct Clear { int operator()(int); // 參數(shù)與回返值的型別相同 double operator()(double); // 參數(shù)與回返值的型別相同};template <class Obj> class Calculus {public: template<class Arg> Arg operator()(Arg& a) const { return member(a); }private: Obj member;};實(shí)體化模板類
Calculus<Clear>,Calculus的仿函數(shù)其回返值總是和Clear的仿函數(shù)其回返值具有相同的類別。然而,若給定類別Confused:struct Confused { double operator()(int); // 參數(shù)與回返值的型別不相同 int operator()(double); // 參數(shù)與回返值的型別不相同};企圖實(shí)體化模板類Calculus<Confused>將導(dǎo)致Calculus的仿函數(shù)其回返值和類別Confused的仿函數(shù)其回返值有不同的類別。對于
int和double之間的轉(zhuǎn)換,編譯器將給出警告。模板
std::result_of被TR1引進(jìn)且被C++11所采納,可允許我們決定和使用一個(gè)仿函數(shù)其回返值的類別。底下,CalculusVer2對象使用std::result_of對象來推導(dǎo)其仿函數(shù)的回返值類別: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;};如此一來,在實(shí)體化
CalculusVer2<Confused>其仿函數(shù)時(shí),不會有類別轉(zhuǎn)換,警告或是錯(cuò)誤發(fā)生。模板
std::result_of在TR1和C++11有一點(diǎn)不同。TR1的版本允許實(shí)現(xiàn)在特殊情況下,可以無法決定一個(gè)函數(shù)調(diào)用其回返值類別。然而,因?yàn)镃++11支持了decltype,實(shí)現(xiàn)被要求在所有情況下,皆能計(jì)算出回返值類別。已被移除或是不包含在C++11標(biāo)準(zhǔn)的特色[編輯]
預(yù)計(jì)由Technical Report提供支持:
模塊十進(jìn)制類別數(shù)學(xué)專用函數(shù)延后討論:
Concepts(概念 (C++))更完整或必備的垃圾回收支持ReflectionMacro Scopes被移除或廢棄的特色[編輯]
循序點(diǎn)(sequence point),這個(gè)術(shù)語正被更為易懂的描述所替換。一個(gè)運(yùn)算可以發(fā)生(is sequenced before)在另一個(gè)運(yùn)算之前;又或者兩個(gè)運(yùn)算彼此之間沒有順序關(guān)系(are unsequenced)。exportexception specificationsstd::auto_ptr被std::unique_ptr替換。仿函數(shù)基類別 (std::unary_function, std::binary_function)、函數(shù)指針適配器、類成員指針適配器以及綁定器 (binder)。編譯器實(shí)現(xiàn)[編輯]
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預(yù)覽版支持的C++11/14/17特性的對比列表:VC2015 Preview語言特性列表GCC 4.8.1已實(shí)現(xiàn)C++11標(biāo)準(zhǔn)的所有主要語言特性:Status of Experimental C++11 Support in GCC 4.8關(guān)系項(xiàng)目[編輯]
C++ Technical Report 1C11,C編程語言的最新標(biāo)準(zhǔn)C++14,C++的最新標(biāo)準(zhǔn)轉(zhuǎn)自:https://zh.wikipedia.org/wiki/C%2B%2B11
新聞熱點(diǎn)
疑難解答
圖片精選