C++0x
C++0x
C++0x是C++11標準成為正式標準之前的草案臨時名字。
C++是具有國際標準的編程語言,通常稱作ANSI/ISO C++,1998年國際標準組織(ISO)頒布了C++語言的國際標準ISO/IEC 1488-1998。。1998年是C++標準委員會成立的第一年,以後每5年視實際需要更新一次標準。2009年,C++標準有了一次更新,一般稱該草案為C++0x。後來,2011年,C++新標準標準正式通過,更名為ISO/IEC 14882:2011,簡稱C++11。
C++標準化委員會的主要焦點是在語言核心的發展上,C++0x關於核心語言的領域將被大幅改善,包括多線程支持、泛型編程、統一的初始化,以及表現的加強。
C++11(草案原名C++0x)最終國際投票已於2011年8月10日結束,所有國家都投出了贊成票。國際標準化組織(ISO)和國際電工委員會(IEC)於 2011年9月1日出版發布C++11標準,正式名稱為:
ISO/IEC 14882:2011 - Information technology -- Programming languages -- C++
C++11標準,作為C++標準第三版,將取代1998年發布的C++標準第一版(C++98標準,全稱ISO/IEC 14882:1998)和2003年發布的C++標準第二版(C++03標準,全稱ISO/IEC 14882:2003)。
新標準的pdf文檔可以在ISO官方網站購買獲取。
C++委員會的主要焦點是在語言核心的發展上。因此C++0x的發表日期取決於這部份標準的作業進度。
核心語言的領域將被大幅改善,包括多線程支持、泛型編程、統一的初始化,以及表現的加強。
在此分成4個區塊來討論核心語言的特色以及變更:運行期表現強化、建構期表現強化、可用性強化,還有嶄新的機能。某些特色可能會同時屬於多個區塊,但在此僅於其最具代表性的區塊描述該特色。
2011年3月27日,IS0 C++ 委員會正式批准了C++編程語言國際標準最終草案(FDIS)。標準本身已經完成,接下來將是根據委員會會議修改意見更新工作草案,預計將用三周時間完成FDIS草案,然後交給日內瓦的ITTF,最新的C++標準將在夏天發布,先前被臨時命名為C++0x的新標準將被稱為C++ 2011。從2003年發布的C++03到2011年的C++ 2011,新標準的制定歷經了8年時間。GCC和Visual C++編譯器都已加入了C++2011/C++0x的支持。
在舊標準C++語言中,臨時量(術語為右值,因其出現在賦值表達式的右邊)可以做參數傳給函數,但只能被接受為const &類型。這樣函數便無法區分傳給const &的是真正的右值還是普通const變數。而且,由於類型為const &,函數也無法改變所傳對象的值。
C++0x將增加一種名為右值引用的新的引用類型,記作typename &&。這種類型可以被接受為非const值,從而允許改變其值。這種改變將允許某些對象創建轉移語義。
比如,一個std::vector,就其內部實現而言,是一個C式數組的封裝。如果需要創建vector臨時量或者從函數中返回vector,那就只能通過創建一個新的vector並拷貝所有存於右值中的數據來存儲數據。之後這個臨時的vector則會被銷毀,同時刪除其包含的數據。
有了右值引用,一個參數為指向某個vector的右值引用的std::vector的轉移構造器就能夠簡單地將該右值中C式數組的指針複製到新的vector,然後將該右值清空。這裡沒有數組拷貝,並且銷毀被清空的右值也不會銷毀保存數據的內存。返回vector的函數現在只需要返回一個std::vector<>&&。如果vector沒有轉移構造器,那麼結果會像以前一樣:用std::vector<> &參數調用它的拷貝構造器。如果vector確實具有轉移構造器,那麼轉移構造器就會被調用,從而避免大量的內存分配。
考慮到安全因素,具名變數即使被聲明為右值類型也不會被當作右值。如需把它當作右值,須使用庫函數std::move()。
bool is_r_value(int &&)
{
return true;
}
bool is_r_value(const int &)
{
return false;
}
void test(int &&i)
{
is_r_value(i); // false
is_r_value(std::move(i)); // true
}
出於右值引用定義的本質特徵以及某些對左值引用(常規引用)定義的修改,右值引用允許程序員提供函數參數的完美轉發。當與模板變參相結合時,這種能力可以允許函數模板完美地將參數轉發給接受那些參數的其他函數。這在轉發構造器參數時尤為有用:可以創建一些能自動調用具有相應參數構造器的工廠函數。
C++語言一直具有常量表達式的概念。這些諸如3+4之類的表達式總是產生相同的結果且不具備副作用。常量表達式給編譯器帶來了優化的可能,而編譯器也經常在編譯期執行此類表達式並將結果存放在程序中。此外,C++語言規範中有一些地方需要使用常量表達式。定義數組需要常量表達式,而枚舉值也必須是常量表達式。
然而,每當碰到函數調用或對象構造,常量表達式便不再有效。所以簡單如下例便不合法:
int GetFive()
{
return 5;
}
int some_value[GetFive() + 5]; //創建一個包含10個整型變數的數組,在標準C++中不合法
這段代碼在C++中不合法,因為GetFive() + 5不是一個常量表達式。編譯器無從知曉GetFive在運行期是否產生常量。理論上,這個函數可能會影響某個全局變數,或者調用其他運行期產生非常量的函數。
C++0x將引入constexpr關鍵字,此關鍵字將使用戶能保證某個函數或構造器在編譯期產生常量。上例可被改寫如下:
constexpr int GetFive()
{
return 5;
}
int some_value[GetFive() + 5]; //在標準C++0x中合法
這段代碼將使編譯器理解並確認GetFive是個編譯期常量。
在函數上使用constexpr將對函數功能施加嚴格的限制。首先,函數必須返回非void類型。其次,函數體必須具有"return /expr/"的形式。第三,expr在參數替換后必須是常量表達式。該常量表達式只能調用其他定義為constexpr的函數,只能使用其他常量表達式數據變數。第四,常量表達式中一切形式的遞歸均被禁止。最後,這種帶constexpr的函數在編譯單元中必須先定義后調用。
變數也可被定義為常量表達式值。
constexpr double forceOfGravity = 9.8;
constexpr double moonGravity = forceOfGravity / 6;
常量表達式數據變數隱含為常量。它們只能存放常量表達式或常量表達式構造器的結果。
為了從用戶自定義類型中構建常量表達式數據值,構造器在聲明時可帶constexpr。同常量表達式函數一樣,在編譯單元中常量表達式構造器也必須先定義后使用。常量表達式構造器函數體必須為空,而且它必須用常量表達式構造其成員。這種類型的析構器必須是平凡的。
由常量表達式拷貝構造的類型也必須被定義為constexpr,以使它們能從常量表達式函數中作為值被返回。類的任何成員函數,包括拷貝構造器和操作符重載,都能被聲明為constexpr,只要它們符合常量表達式函數的定義。這就允許編譯器在編譯期不僅能拷貝類對象,也能對其實施其他操作。
常量表達式函數或構造器可以用非constexpr參數來調用。就如同一個constexpr整數常量可以被賦給一個非constexpr變數一樣,constexpr函數也可用非constexpr參數來調用,並且其結果也可存放在非constexpr變數中。此關鍵字只是提供了在一個表達式的全部成員均為constexpr時其結果為編譯期常量的可能性。
在標準C++語言中,要讓結構成為POD類型必須滿足某幾條規則。有充分理由讓一大堆類型滿足這些規則(定義);只要滿足這些規則,結構的實現將產生兼容於C的對象布局。然而,在C++03中這些規則過於嚴格。註:POD,Plain Old Data,指POD用來表明C++中與C相兼容的數據類型,可以按照C的方式來處理(運算、拷貝等)。非POD數據類型與C不兼容,只能按照C++特有的方式進行使用。
C++0x將放鬆某些關於POD的限制規則。
如果一個類或結構是平凡的,具有標準布局的,且不包含任何非POD的非靜態成員,那麼它就被認定是POD。平凡的類或結構定義如下:
1.具有一個平凡的預設構造器。(可以使用預設構造器語法,如 SomeConstructor() = default;)
2.具有一個平凡的拷貝構造器。(可以使用預設構造器語法)
3.具有一個平凡的拷貝賦值運算符。(可以使用預設語法)
4.具有一個非虛且平凡的析構器。
一個具有標準布局的類或結構被定義如下:
1.所有非靜態數據成員均為標準布局類型。
2.所有非靜態成員的訪問許可權(public, private,protected) 均相同。
3.沒有虛函數。
4.沒有虛基類。
5.所有基類均為標準布局類型。
6.沒有任何基類的類型與類中第一個非靜態成員相同。
7.要麼全部基類都沒有非靜態數據成員,要麼最下層的子類沒有非靜態數據成員且最多只有一個基類有非靜態數據成員。總之繼承樹中最多只能有一個類有非靜態數據成員。所有非靜態數據成員必須都是標準布局類型。
外部模板
在標準C++語言中,如果在某一個編譯單元中編譯器碰到一個參數完全指定的模板,它就必須實例化該模板。這種做法可能大大延長編譯時間,尤其在許多編譯單元使用同樣的參數實例化該模板時。
C++0x將引入外部模板的概念。C++已經擁有了迫使編譯器在某一地點實例化模板的語法:
template class std::vector;
C++所缺乏的是防止編譯器具現化某個模板的能力。C++0x只是簡單地將語法擴展為:
extern template class std::vector;
這段代碼將告訴編譯器不要在這個編譯單元實例化此模板。
標準C++語言從C語言中借入了初始化列表概念。根據這一概念,結構或數組可以通過給定一串按照結構中成員定義的次序排列的參數來創建。初始化列表可以遞歸創建,因此結構數組或包含其他結構的結構也能使用初始化列表。這對於靜態列表或用某些特定值初始化結構而言非常有用。C++語言中存在能讓對象初始化的構造器特性。但構造器特性本身並不能取代初始化列表的所有功能。標準C++允許類和結構使用初始化列表,但它們必須滿足POD的定義。非POD的類不能使用初始化列表,一些C++式的容器如std::vector和boost::array也不行。
C++0x將把初始化列表綁定為一種名為std::initializer_list的類型。這將允許構造器及其他函數接受初始化列表作為其參數。比如:
classSequenceClass
{
public:
SequenceClass(std::initializer_list < int >list);
};
這段代碼將允許SequenceClass用一串整數構造,如下所示:
SequenceClass someVar = {1, 4, 5, 6};
這種構造器是一種特殊類型的構造器,名為初始化列表構造器。具有這種構造器的類在統一的初始化形式中將被特殊對待。
std::initializer_list<>類在C++0x標準庫中將成為一等公民。但是這個類的對象只能通過使用{}語法由C++0x編譯器靜態構建並初始化。列表一旦構建即可被拷貝,儘管只是引用拷貝。初始化列表是常量,一旦構建,組成列表的成員以及其成員所包含的數據便無法改變。
由於初始化列表是一種真實的類型,因此在類構造器之外的地方也能使用。常規函數也可接受初始化列表作為其參數。比如:
void FunctionName(std::initializer_list list);
FunctionName({1.0f, -3.45f, -0.4f});
另外,標準容器也可用初始化列表初始化。比如:
vector DayOfWeek={"Monday", "Tuesday", "Wednesday"};
標準C++在類型初始化中存在一些問題。語言中存在幾種類型初始化方式,但替換使用的話產生的結果不盡相同。傳統的構造語法看起來更像函數聲明。必須採取措施以使編譯器不把對象構造誤認為函數聲明。只有集合類型和POD類型能用集合初始化器初始化(用SomeType var = {};).
C++0x將提供一種能作用於任何對象的完全統一的類型初始化形式。這種形式對初始化列表語法作了擴展:
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式的初始化列表。每個public變數都將用初始化列表中的值初始化。如果需要,隱式類型轉化將被使用,並且如果沒有隱式類型轉化可供使用,編譯器將報告編譯失敗。
var2的初始化只是簡單地調用構造器。
統一的初始化對象構造將消除在某些情況下指定類型的需要:
struct IdString
{
std::string name;
int identifier;
};
IdString var3{"SomeName", 4};
這種語法會自動使用const char *調用std::string進行初始化。程序員也可以使用下面的代碼:
IdString GetString()
{
return {"SomeName", 4}; //注意,這裡沒寫return ldString{"SomeName", 4}
}
統一的初始化形式不會取代構造器語法。某些情況下仍然需要構造器語法。如果一個類具有初始化列表構造器(TypeName(initializer_list);),,那麼只要初始化列表符合該構造器的類型,初始化列表構造將優先於其他構造形式。
C++0x版本的std::vector將擁有匹配與模板參數的初始化列表構造器。這就意味著下面這段代碼:
std::vector theVec{4};
這段代碼將調用初始化列表構造器,而不會調用std::vector中接受單個長度參數並創建相應長度的vector的構造器。為了調用后一個構造器,用戶需要直接使用標準構造器語法。
在標準的C++和C語言中,變數在使用時必須明確指定其類型。然而,隨著模板類型及模板元編程的到來,表述某些定義完好的函數的返回值的類型變得不那麼容易了。由此,在函數中存儲中間值也變得困難,用戶有可能需要了解某個模板元編程庫的內部結構才行。
C++0x將通過兩種方式來緩解這些困難。首先,帶有明確初始化的變數定義將可以使用auto關鍵字。這種初始化將創建與初始化器類型相同的變數。
auto someStrangeCallableType = boost::bind(&SomeFunction, _2, _1, someObject);
auto otherVariable = 5;
someStrangeCallableType的類型將等同於任何由boost::bind所返回的適合於這些特定參數的模板函數的類型。編譯器很容易知道其類型,用戶則不然。
otherVariable的類型也定義完好,但用戶更容易推定其類型。該變數是整型,也就是整型常量的類型。
另外,關鍵字decltype可用於在編譯期確定某個表達式的類型。比如:
int someInt;
decltype(someInt) otherIntegerVariable = 5;
這種用法相對於auto可能更有效,因為auto變數的類型只有編譯器才知道。而且,對於那些大量使用操作符重載及特化類型的代碼,使用decltype來推導表達式的類型也很有用。
auto在減少代碼冗餘性方面也很有用。比如,寫下面這段代碼時:
for(vector::const_iteratoritr= myvec.begin(); itr != myvec.end(); ++itr)
程序員可以使用下面這種更短的形式:
for (autoitr= myvec.begin(); itr != myvec.end(); ++itr)
當程序員在開始使用嵌套容器時,這兩段代碼的區別將更加明顯,儘管在這種情況下使用typedef也是一種減少代碼的好方法。
以範圍為基礎的 for 循環(for each)
C++庫Boost定義了幾個區間概念。區間代表了與容器相類似的列表中兩點之間的可控列表。已序容器是區間的超集。已序容器中的兩個迭代器也能定義一個區間。這些概念和演演算法都將被融入C++0x的標準庫中。然而,C++0x還將提供一種專用的語言設施來運用區間概念。
for語句將使區間概念上的循環更容易:
int my_array[5] = { 1, 2, 3, 4, 5 };
for(int &x: my_array)
{
x *= 2;
}
新的for循環的第一部分定義了用於在區間上循環的變數。和普通for循環中聲明的變數一樣,該變數的作用域也僅限於循環之內。置於":"之後的第二部分則表示將進行循環的區間。在這種情況下,存在一個約束映射可以將C式數組轉化為區間。
進行循環的區間還可以是std::vector,或任何符合區間概念的對象。
Lambda函數與Lambda表達式(內部函數)
在標準C++語言中,尤其在使用諸如sort和find之類的標準庫演演算法函數時,用戶總是希望在演演算法函數調用的觸發點附近定義謂詞函數。在這一方面語言中只有一種機制可供利用:在函數中定義類。通常這種做法既啰嗦又笨重。另外,標準C++語言不允許在函數中定義的類充當模板參數,所以這種做法行不通。
顯而易見,解決方案在於允許定義lambda表達式和 lambda函數。C++0x將允許定義lambda函數。
lambda函數可以定義如下:
[](int x, int y) { return x + y }
此無名函數的返回值類型為decltype(x+y)。只有lambda函數的形式為"return /expression/"時,返回值類型才能省略。因此這種lambda函數內部只能有一句語句。
返回值類型也可像下例那樣明確指定。一個更為複雜的例子:
[](int x, int y) -> int { int z = x + y; return z + x; }
在此例中,臨時變數z被創建並用於存儲中間值。和普通函數一樣,中間值在多次函數調用之間不會被保存。
如果lambda函數不返回值,即返回值類型為void的話,該返回值類型也可完全省略。
在lambda函數作用域範圍內被定義的變數的引用也能被使用。這些變數的合集通常被稱為閉包。閉包可以定義並使用如下:
std::vector someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&total](int x) {
total += x
});
std::cout << total;
這段代碼將顯示列表中所有元素的總和。變數total將被存為該lambda函數相應的閉包的一部分。由於閉包變數total是棧變數total的引用,使用前者可以改變後者的值。
為棧變數生成的閉包變數也可以不用引用操作符/&/定義,這種情況下lambda函數將拷貝其值。這種做法將促使用戶明確聲明其意圖:是引用棧變數還是拷貝棧變數。引用棧變數可能會產生危險。如果某個lambda函數將在所創建的作用域之外被引用(比如將此lambda函數存放在std::function(C++0x標準)對象中可以做到這一點),那麼用戶必須保證該lambda函數沒有引用任何棧變數。
對於那些可以保證只在其所創建的作用域內被執行的lambda函數,使用棧變數無須通過顯式引用:
std::vector someList;
int total = 0;
std::for_each(someList.begin(), someList.end(), [&](int x) {
total += x
});
這種lambda函數的具體內部實現可能會有所不同,但可以預期這種lambda函數可能不會保存所有棧變數的引用而是會保存函數創建時的棧指針。
如果不用[&]而用[=],那麼所有被引用的變數都將被拷貝,從而允許lambda函數在原有變數生命期結束后仍然能夠被使用。
預設指示符還能和參數列表結合使用。比如,如果用戶希望只拷貝其中一個變數的值,而對其他變數使用引用,則可使用下面的代碼:
int total = 0;
int value = 5;
[&, value](int x) { total += (x * value) };
這段代碼將導致total被存為引用,而value則會被存為拷貝。
如果一個lambda函數由一個類的某個成員函數定義,那麼此lambda函數便被認定為該類的友元。這種lambda函數可以使用屬於該類類型的對象的引用並訪問其內部成員。
[](SomeType *typePtr) { typePtr->SomePrivateMemberFunction() };
只有當lambda函數在SomeType的某個成員函數中創建時這段代碼才能工作。
對於指向當前成員函數所隸屬對象的this指針,其處理有些特殊:必須在lambda函數中明確指定。
[this]() { this->SomePrivateMemberFunction() };
使用[&] 或 [=]形式將使this自動可用。
Lambda函數是一些類型取決於編譯器的函數對象。它們的類型只對編譯器開放。如果用戶希望把lambda函數當作參數,那麼要麼參數相應類型為模板,要麼創建一個std::function用於保存lambda函數。使用auto關鍵字則可以將lambda函數保存在局部變數中。
auto myLambdaFunc = [this]() {
this->SomePrivateMemberFunction()
};
然而,如果lambda函數的所有閉包變數均為引用,或者lambda函數根本沒有閉包變數,那麼所產生的函數對象將具有一種特殊類型:std::reference_closure。其中R(P)是帶返回值的函數簽名。這樣做的理由在於期望此種類型的效率能好於使用std::function。
std::reference_closure 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相加所產生的類型。即使有了前面所講述的C++0x的decltype功能,仍然不行:
template< typename LHS, typename RHS>
decltype(lhs+rhs) AddingFunc(const LHS &lhs, const RHS &rhs)
{
return lhs + rhs;
}
這一段並非合法的C++代碼,因為lhs和rhs尚未定義,只有在詞法分析器分析出函數原型的其餘部分之後這兩者才能成為有效的標識符。
為解決這一問題,C++0x將引入一種新型的函數定義和聲明的語法:
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;
}
在C++語言中,模板類和模板函數必須對它們所接受的類型施加某些限制。比如,STL容器要求容器中的類型必須可以賦值。與類繼承所展示的動多態(任何能接受Foo&類型對象作為參數的函數也能傳入Foo的子類型)有所不同,任何類只要支持某個模板所使用的操作,它就能被用於該模板。在函數傳參數的情況下,參數所必須滿足的需求是清晰的(必須是Foo的子類型),而模板的場合下,對象所需滿足的介面則是隱含在模板實現當中的。約束則提供了一種將模板參數所必需滿足的介面代碼化的機制。
引入約束的最初動因在於改進編譯錯誤信息的質量。如果程序員試圖使用一種不能提供某個模板所需介面的類型,那麼編譯器將產生錯誤信息。然而,這些錯誤信息通常難以理解,尤其對於新手而言。首先,錯誤信息中的模板參數通常被完整拼寫出來,這將導致異常龐大的錯誤信息。在某些編譯器上,簡單的錯誤會產生好幾K的錯誤信息。其次,這些錯誤信息通常不會指向錯誤的實際發生地點。比如,如果程序員試圖創建一個其成員為不具備拷貝構造器對象的vector,首先出現的錯誤信息幾乎總是指向vector類中試圖拷貝構造其成員的那段代碼。程序員必須具備足夠的經驗和能力才能判斷出實際的錯誤在於相應類型無法完全滿足vector所需要的介面。
在試圖解決此問題的過程中,C++0x為語言添加了約束這一特性。與OOP使用基類來限制類型的功能相似,約束是一種限制類型介面的具名結構。而與OOP所不同的是,約束定義並非總是與傳入模板的參數類型明確相關,但它總是與模板定義相關:
template < LessThanComparable T >
const T & min(const T & x, const T & y)
{
return y < x ? y : x;
}
這裡沒有用/class /或/ typename/將模板參數指定為任意類型,而是使用了/LessThanComparable/這個之前定義的約束。如果某個傳入/min/模板參數的類型不符合/LessThanComparable/約束的定義,那麼編譯器將報告編譯錯誤,告訴用戶用來具現化該模板的類型不符合/LessThanComparable/約束。
下面是一個更一般化的約束形式:
template requires LessThanComparable
const T& min(const T &x, const T &y)
{
return y < x ? y : x;
}
關鍵字/requires/之後為一串約束的聲明。它可以被用於表述涉及多個類型的約束。此外,如果用戶希望當類型匹配該約束時不要使用某個特定模板,也可以用/requires !LessThanComparable/。可以像模板特化那樣使用這種機制。一個通用模板可能通過顯式禁用一些特性豐富的約束來處理具有較少特性的類型。而這些約束則可通過特化利用某些特性來取得更高的效率並實現更多的功能。
約束定義如下:
auto concept LessThanComparable < typename T >
{
bool operator<(T, T);
}
這個例子中的關鍵字/auto/意味著任何類型只要支持約束中所指定的操作便被認定支持該約束。如果不使用/auto/關鍵字,為聲明某個類型支持該約束就必須對該類型使用約束映射。
該約束聲明任何類型只要定義了接受兩個參數並返回bool型的<操作符就被認為是/LessThanComparable/。該操作符不一定是一個自由函數,它也可以是T類型的成員函數。
約束也可以涉及多個類型。比如,約束能表示一個類型可以轉換為另一個類型:
auto concept Convertible < typename T, typename U >
{
operator U(const T &);
}
為了在模板中使用這個約束,模板必須使用一種更為一般化的形式:
template < typename U, typename T >
requires Convertible < T, U > U convert(const T & t)
{
return t;
}
約束可以組合運用。比如,給定一個名為/Regular/的約束
concept InputIterator < typename Iter, typename Value >
{
requires Regular < Iter >;
Value operator*(const Iter &);
Iter & operator++(Iter &);
Iter operator++(Iter &, int);
}
/InputIterator/約束的第一個模板參數必須符合/Regular/約束。
與繼承相似,約束也可派生自另一約束。與類繼承相似,滿足派生約束所有限制條件的類型必須滿足基本約束的所有限制條件。約束派生定義形同類派生:
concept ForwardIterator :
InputIterator
{
//Add other requirements here.
}
類型名可以與約束相關。這將施加一些限制條件:在使用這些約束的模板中,這些類型名可供使用:
concept InputIterator < typename Iter >
{
typename value_type;
typename reference;
typename pointer;
typename difference_type;
requires Regular < Iter >;
requires Convertible < reference, value_type >;
reference operator*(const Iter &); // dereference
Iter & operator++(Iter &); // pre-increment
Iter operator++(Iter &, int); // post-increment
// ...
}
約束映射允許某些類型被顯式綁定到某個約束。如有可能,約束映射也允許在不改變類型定義的前提下讓該類型採用某個約束的語法。比如下例:
concept_map InputIterator < char *>
{
typedef char value_type;
typedef char &reference;
typedef char *pointer;
typedef std::ptrdiff_t difference_type;
};
這個約束映射填補了當/InputIterator/映射作用於/char*/類型時所需要的類型名。
為增加靈活性,約束映射本身也能被模板化。上例可以被延伸至所有指針類型:
template < typename T > concept_map InputIterator < T * >
{
typedef T value_type;
typedef T & reference;
typedef T *pointer;
typedef std::ptrdiff_t difference_type;
};
此外,約束映射也可充當迷你類型,此時它會包含函數定義以及其他與類相關的結
構設施:
concept Stack < typename X >
{
typename value_type;
void push(X &, const value_type &);
void pop(X &);
value_type top(const X &);
bool empty(const X &);
};
template < typename T > concept_map Stack < std::vector < T > >
{
typedef T value_type;
void push(std::vector < T > &v, const T & x)
{
v.push_back(x);
}
void pop(std::vector < T > &v)
{
v.pop_back();
}
T top(const std::vector < T > &v)
{
return v.back();
}
bool empty(const std::vector < T > &v)
{
return v.empty();
}
};
這個約束映射將允許任何接受實現了/Stack/約束的類型的模板也接受
/std::vector/,同時將所有函數調用映射為對/std::vector/的調用。最終,這種
做法將允許一個已經存在的對象在不改變其定義的前提下,轉換其介面並為模板函
數所利用。
最後需要指出的是,某些限制條件可以通過靜態斷言來檢測。這種手段可以用來檢
測那些模板需要但卻面向其他方面問題的限制條件。
在標準C++語言中,構造器不能調用其他構造器。每個構造器要麼獨自構建類的所有成員要麼調用某個公共成員函數。基類的構造器不能直接暴露給派生類:即便基類的構造器更合適,子類也必須實現自己的構造器。類的非靜態數據成員不能在其聲明的場所初始化,它們只能在構造器中初始化。
C++0x將為所有這些問題提供解決方案。
C++0x將允許構造器調用其他夥伴構造器(被稱為委託)。如此,只需添加少量代碼,構造器便能利用其他構造器的行為。另外一些語言,比如Java和C#,允許這樣做。語法如下:
class SomeType
{
int number;
public:
SomeType(int newNumber):number(newNumber) {}
SomeType():SomeType(42) {}
};
這就產生了一個問題:C++03認為一個對象在其構造器執行完畢時才能構建完成,而C++0x則認為一個對象在任何構造器執行完畢時都將構建完成。由於多個構造器被允許執行,這將導致每個委託構造器都可能在一個已經構造完成的對象上執行操作。派生類構造器將在基類的所有委託構造器執行完畢后執行。
關於基類構造器,C++0x將允許一個類指定需要繼承的基類構造器。這意味著C++0x編譯器將產生代碼用於類繼承,即將子類構造轉發為基類構造。注意這是一個要麼全部要麼沒有的特性:或者全部構造器被轉發,或者沒有構造器被轉發。另外注意在多重繼承的情況下有些限制,比如類構造器不能繼承來自兩個類的具有相同簽名的構造器。同時子類構造器的簽名也不能與用來繼承的基類構造器相匹配。
相應語法如下:
class BaseClass
{
public:
BaseClass(int iValue);
};
class DerivedClass:public BaseClass
{
public:
using default BaseClass;
};
關於成員初始化,C++0x將允許以下語法:
class SomeClass
{
public:
SomeClass() {}
explicit SomeClass(int iNewValue):iValue(iNewValue) {}
private:
int iValue = 5;
};
該類的任何構造器都將把iValue初始化為5,除非它提供自己的實現來改變這種行
為。所以上面的空構造器將按照類的定義來初始化iValue,而接受int的構造器則
會用給定的參數來初始化iValue。
在現行標準中,常量0既是常量整數又是空指針,充當著雙重角色。這一行為自1972年C語言早期以來便一直存在。
多年來,程序員為了避免這種語義模糊多採用標識符NULL來代替0。然而,兩項C++語言的設計選擇集中產生了另一項語義模糊。在C語言中,NULL作為預編譯宏被定義為((void*)0)或0。在C++語言中,void*不能隱式轉換為其他指針類型,因而在前項定義下,簡單如char* c = NULL的代碼會通不過編譯。為解決此問題,C++確保NULL展開為0,並作為一種特例允許其轉換為其他指針類型。這一選擇在同重載機制交互時產生了麻煩。比如,假設程序中存在以下聲明:
void foo(char *);
void foo(int);
然後調用foo(NULL),則foo(int)版本將會被調用,幾乎可以肯定這不是程序員的意圖。
新標準很可能引入一個專用於空指針的關鍵字,C++11使用nullptr承擔這一角色。
nullptr不能被賦給整型,也不能與整型相比較,但它可以向其他指針類型賦值並與之比較。
0的現存角色將會因顯而易見的兼容性理由而得到保留。
如果新的語法取得成功,C++委員會可能會將把0和NULL當作空指針的做法標記為已廢棄特性,並最終廢棄這種雙重角色。
在標準C++語言中,枚舉不是類型安全的。枚舉值實際上就是整數,即便其枚舉類型各不相同。這樣不同枚舉類型的兩個枚舉值相互比較就成為可能。這方面C++03所提供的唯一安全特性為:一個整數或一種枚舉類型的值不能隱式轉換為其他枚舉類型的值。另外,枚舉所使用的整形的大小無法由用戶指定,而只能由實現定義。最後,枚舉值的作用域為枚舉的外圍作用域。這就意味著兩個不同的枚舉不能包含名字相同的成員。
C++0x將允許創建一類沒有上述問題的特殊枚舉。這種枚舉通過enum class來聲明:
enum class Enumeration
{
Val1,
Val2,
Val3 = 100,
Val4 ,
};
這種枚舉是類型安全的。枚舉類的值不會被隱式轉換為整數,這樣它們也不會被拿來與整數相比較。(Enumeration::Val4 == 101會產生編譯錯誤)
枚舉類所使用的類型可以明確指定。預設情況下,如同上例,為int。但這也能像下例那樣改變:
enum class Enum2 : unsigned int {Val1, Val2};
這種枚舉的作用域也被指定為枚舉類名的作用域。使用這種枚舉名必須顯式指定作用域。Val1無定義,而Enum2::Val1有定義。
另外,C++0x也將允許標準枚舉提供其作用域範圍以及其使用的整型大小。
enum Enum3 : unsigned long {Val1 = 1, Val2};
這種枚舉名被定義具有枚舉類型作用域,如(Enum3::Val1)。但是,為了保持向後兼容,枚舉名也被放入外圍作用域。
枚舉的前置聲明在C++0x中也將成為可能。以前,枚舉類型不能前置聲明的理由是枚舉的大小取決於其內容。只要枚舉值的大小在程序中得以指定,枚舉就能前置聲明。
enum Enum1; //在C++和C++0x均合法,未明確標明枚舉數大小
enum Enum2 : unsigned int; //僅C++0x合法
enum class Enum3; //在C++0x中合法,枚舉數被隱式確定為int型
enum class Enum4: unsigned int; //在C++0x中合法
enum Enum2 : unsigned short; //不合法,Enum2已被定義為不同類型
標準C++的詞法分析器在任何場合下都將">>"解釋為右移操作符。然而,在模板定義中,如此解釋兩個右尖括弧幾乎總是錯誤的。
C++0x將改變詞法分析器的規範以便在合理的情況下能把多個右尖括弧解釋為模板參數列表的結束符。可以用小括弧來改變這種行為。
template SomeType;
std::vector2>> x1;// 解讀為 std::vector of "SomeType 2>",非法。整數 1 被隱形轉換為 bool 類型值 true
std::vector2)>> x1;// 解讀為 std::vector of "SomeType", 合法的 C++0x 表示式, (1>2) 被轉換為 bool 類型值 false
標準C++為構造器添加了explicit關鍵字作為修飾符以防止只有單個參數的構造器使用隱式轉換操作。然而,這種做法對於真正的轉換操作符是無效的。比如,一個智能指針類可能有一個bool()操作符以使自身的行為更像原生指針。如果它包含這種轉換操作,它就能用if(smart_ptr_variable)來測試。(若指針非空則測試是為真,否則測試為假。)然而,這種操作也會導致其他出乎意料的轉換。由於C++的bool被定義為一種算術類型,這種指針也就可以被隱式轉換為整型甚而浮點類型,從而導致一些用戶並不希望出現的算術操作。
在C++0x中,explicit關鍵字將能用於轉換操作符的定義中。與構造器一樣,它將防止進一步的隱式轉換。
在進入這個主題之前,各位應該先弄清楚“模板”和“類型”本質上的不同。class template (類模板,是模板)是用來產生 template class (模板類,是類型)。
在標準 C++,typedef 可定義模板類一個新的類型名稱,但是不能夠使用 typedef 來定義模板的別名。舉例來說:
template
class SomeType;
template
typedef SomeType TypedefName; // 在C++是不合法的
不能通過編譯。
為了定義模板的別名,C++0x 將會增加以下的語法:
template
class SomeType;
template
using TypedefName = SomeType;
using 也能在 C++0x 中定義一般類型的別名,等同 typedef:
typedef void(*PFD)(double);// 傳統語法
using PF = void (*)(double); // 新增語法
在標準 C++ 中,並非任意的類型都能做為 union 的成員。比方說,帶有非預設構造函數的類型就不能是 union 的成員。在新的標準里,移除了所有對 union 的使用限制,除了其成員仍然不能是引用類型。這一改變使得 union 更強大,更有用,也易於使用。
以下為 C++0x 中 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++0x
};
這一改變僅放寬 union 的使用限制,不會影響既有的舊代碼。
在 C++0x 之前, 不論是模板類或是模板函數,都只能按其被聲明時所指定的樣子,接受一組固定數目的模板實參; C++0x 加入新的表示法,允許任意個數、任意類別的模板實參,不必在定義時將實參的個數固定。
templateclass tuple;
模板類tuple 的對象,能接受不限個數的 typename 作為它的模板形參:
class tuple, std::map>> someInstanceName;
實參的個數也可以是 0,所以 class tuple<> someInstanceName 這樣的定義也是可以的。
若不希望產生實參個數為 0 的變長參數模板,則可以採用以下的定義:
templateclass tuple;
變長參數模板也能運用到模板函數上。傳統 C 中的printf 函數雖然也能達成不定個數的形參的調用,但其並非類型安全。以下的樣例中,C++0x 除了能定義類別安全的變長參數函數外,還能讓類似 printf 的函數能自然地處理非自帶類別的對象。除了在模板實參中能使用...表示不定長模板實參外,函數實參也使用同樣的表示法代表不定長實參。
template
void printf(const std::string& strFormat,Params... parameters);
其中,Params 與 parameters 分別代表模板與函數的變長參數集合,稱之為實參包 (parameter pack)。實參包必須要和運算符“...”搭配使用,避免語法上的歧義。
變長參數模板中,變長參數包無法如同一般實參在類或函數中使用;因此典型的手法是以遞歸的方法取出可用實參。
標準C++提供兩種字元串常量。第一種,包含在雙引號之間,生成一個以'\0'結尾的const char類型的數組。第二種,形為L"",生成一個以L'\0'結尾的constwchar_t類型的數組。這裡wchar_t為寬字元。這兩種字元串常量均沒有對Unicode編碼的字元串提供支持。
為了改進C++編譯器對Unicode的支持,char類型的定義被修改成除了包含編譯器基本執行字符集的所有成員之外還將至少包括UTF-8中的8位編碼。以前它只包含前者。
C++0x將包括三種Unicode編碼的字元串分別用於支持UTF-8,UTF-16,及 UTF-32。
標準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。
C++標準委員會計劃為多線程提供標準化支持。
有兩部分將被涉及:對允許多線程在同一個程序中共存的內存模型提供定義,對這些線程間的交互提供支持。後者將通過類設施來實現。
內存模型對於描述多個線程在何種情況下可以訪問相同的內存地址是不可或缺的。
一個遵守相應規則的程序可以保證運行正常,而一個破壞相應規則的程序則可能會因為編譯器的優化行為以及內存一致性等問題而出現不可預料的行為。
在多線程環境下,每個線程通常都擁有一些自己所獨有的變數。對於函數的局部變數來說確實如此,而對於全局以及靜態變數來說就不是這樣。
除了現存的靜態,動態以及自動存儲方式以外,在下一個標準中還將提議增加一種局部於線程的存儲方式。線程局部存儲方式將通過thread_local存儲指示符來指定。
一個本來可能具有靜態存儲方式的對象(即生命期跨越程序整個執行過程的對象)可能會被賦予線程存儲方式。與靜態存儲的對象相似,線程局部的對象應該也能用構造器構建並用析構器銷毀。
顯式使用/不使用C++類的某些(預設)成員函數在標準C++語言中,如果對象自己不提供,編譯器會自動產生一個預設構造器,一個拷貝構造器,一個拷貝賦值運算符operator=,以及一個析構器。如前所述,用戶可以通過提供自己的版本來覆蓋這些預設實現。C++還定義了一些能作用於所有類的全局操作符(如operator=和operator new),當然用戶也能覆蓋其實現。
問題在於用戶對於這些默認產生的函數缺乏控制。例如,如果需要使某個類不可拷貝,用戶需要聲明一個私有的拷貝構造器,一個拷貝賦值運算符,同時略去這些函數的定義。試圖使用這些函數將產生編譯錯誤或鏈接錯誤。然而這不是一個理想的解決方案。
而且,對於預設構造器而言,明確告訴編譯器產生這類函數通常非常有用。如果對象已經具有了任何構造器,編譯器就不會為它提供預設構造器。這在許多場合下的確有用,但有時也需要同時擁有一個編譯器產生的預設構造器以及另一個專用構造器。
C++0x將允許用戶明確指定使用或不使用這些標準的對象函數。比如,下面這個類型明確聲明它會使用預設構造器。
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;
};
這個對象只能在棧裡面分配或成為其他類型的成員。不使用不可移植的手段將無法在堆中分配該對象。由於使用就地分配new操作符是調用構造器在用戶所分配的內存中創建對象的唯一途徑,並且這一途徑已經被上述代碼所禁止,可以肯定對象將無法被適當創建。
指示符= delete可被用于禁止調用任意函數,當然它也能用于禁止調用具有某些特定參數的成員函數。比如:
struct NoDouble
{
void f(int i);
void f(double) = delete;
};
編譯器將會拒絕用double值調用f()的企圖,而不會無聲無息地將其轉換為int。這種方法可以被一般化從而禁止使用int以外的任何類型調用該函數,代碼如下:
struct OnlyInt
{
void f(int i);
template void f(T) = delete;
};
在32位系統中,存在一種至少64位的long long整數類型十分有用。C99標準將此類型引入標準C,大多數C++編譯器也早就將它作為一種擴展。事實上,一些編譯器在C99引入它很久之前就已經提供支持。C++0x將把這種類型加入標準C++。
C++提供兩種方式測試斷言,宏assert以及預編譯命令#error,但是這兩者對於模版來說都不合用。宏在運行期測試斷言,而預編譯命令則在預編譯期測試斷言,這時候模版還未能實例化。所以它們都不適合來測試牽扯到模板實參的相關特性。
新的機能會引進新的方式可以在編譯期測試assertion,只要使用新的關鍵字static_assert。聲明採取以下的形式:
static_assert(constant-expression,error-message) ;
這裡有一些如何使用static_assert的例子:
static_assert(3.14< GREEKPI && GREEKPI <3.15, "GREEKPI is inaccurate!");
template
struct Check {
static_assert(sizeof(int)<=sizeof(T), "T is not big enough!");
};
允許在沒有提供類實例的前提下作用於類的成員,無需明確的對象。
在標準C++,sizeof可以作用在對象以及類別上。但是不能夠做以下的事:
struct SomeType { OtherType member;};sizeof(SomeType::member);// 直接由SomeType型別取得非靜態成員的大小
這會傳回OtherType的大小。C++03並不允許這樣做,所以會引發編譯錯誤。C++0x將會允許這種使用。
C++0x不會直接提供透明垃圾收集機制。作為替代,C++0x標準將包含一些有利於在C++實現垃圾收集機制的特性。
對垃圾收集機制的完全支持將會延遲到標準的下一個版本,或者是一個技術報告。
Feature | VS11 | g++ 4.7 | Clang 3.1 |
auto | Yes | Yes | Yes |
decltype | Yes | Yes | Yes |
Rvalue references and move semantics | Yes | Yes | Yes |
Lambda expressions | Yes | Yes | Yes |
nullptr | Yes | Yes | Yes |
static_assert | Yes | Yes | Yes |
Range based for loop | Yes | Yes | Yes |
Trailing return type in functions | Yes | Yes | Yes |
final method keyword | Yes | Yes | Yes |
override method keyword | Yes | Yes | Yes |
Strongly typed enums | Yes | Yes | Yes |
Forward declared enums | Yes | Yes | Yes |
extern templates | Yes | Yes | Yes |
>> for nested templates | Yes | Yes | Yes |
Local and unnamed types as template arguments | Yes | Yes | Yes |
Variadic macros | Yes | Yes | Yes |
New built-in types | Partial | Yes | Yes |
Initializer lists | No | Yes | Yes |
explicit type conversion operators | No | Yes | Yes |
Inline namespaces | No | Yes | Yes |
sizeof on non-static data members without an instance | No | Yes | Yes |
Changed restrictions on union members | No | Yes | Yes |
Raw string literals | No | Yes | Yes |
User defined literals | No | Yes | Yes |
Encoding support in literals | No | Yes | Yes |
Arbitrary expressions in template deduction contexts | No | Yes | Yes |
Defaulted methods | No | Yes | Yes |
Deleted methods | No | Yes | Yes |
Non-static data member initializers | No | Yes | Yes |
Variadic templates | No | Yes | Yes |
Default template arguments in function templates | No | Yes | Yes |
Template aliases | No | Yes | Yes |
Forwarding constructors | No | Yes | Yes |
noexcept | No | Yes | Yes |
constexpr | No | Yes | Yes |
Alignment support | Partial | No | Yes |
Rvalue references for *this | No | No | Yes |
C99 compatibility | Partial | Partial | Partial |
Thread local storage | Partial | Partial | No |
Inheriting constructors | No | No | No |
Generalized attributes | No | No | No |
通過對比可以發現,Clang在大多數C++11功能實現上處於領先地位,而Visual Studio則稍顯落後。當然,這三個編譯器都有著不錯的子集適用於跨平台開發。
你可以使用類型推斷、移動語義、右值引用、nullptr,static_assert,range-based參考對比。同時你還可以使用最終和重寫關鍵字來進行友好的控制。此外,你還可以通過Enums(例舉)強類型和提前聲明,這裡有幾個改進后的模板包括extern keyword。
遺憾的是,Visual Studio並不支持較多請求的可變參數模板。另一方面,可變參數宏在這三款編譯器中只支持C99標準。繼承構造函數和廣義屬性這些特性並不是在任何地方都能獲得支持。本地線程存儲是是支持情況最好的一部分(通過非關鍵字標準)。