基本內容
從實踐角度講,它能夠完美解決C++中長久以來為人所詬病的臨時對象效率問題。從語言本身講,它健全了C++中的引用類型在左值右值方面的缺陷。從庫設計者的角度講,它給庫設計者又帶來了一把利器。從庫使用者的角度講,不動一兵一卒便可以獲得"免費的"效率提升…
在標準C++語言中,臨時量(術語為右值,因其出現在賦值表達式的右邊)可以被傳給函式,但只能被接受為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]; //create an array of 10 integers. illegal C++
這段代碼在C++中不合法,因為GetFive() + 5不是一個常量表達式。編譯器無從知
曉GetFive在運行期是否產生常量。理論上,這個函式可能會影響某個全局變數,
或者調用其他運行期產生非常量的函式。
C++0x將引入constexpr關鍵字,此關鍵字將使用戶能保證某個函式或構造器在編譯
期產生常量。上例可被改寫如下:
constexpr int GetFive()
{
return 5;
}
int some_value[GetFive() + 5]; //create an array of 10 integers. legal 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中這些規則過於嚴格。
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的類型。這將允許構
造器及其他函式接受初始化列表作為其參數。比如:
class SequenceClass
{
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});
標準C++在類型初始化中存在一些問題。語言中存在幾種類型初始化方式,但替換
使用的話產生的結果不盡相同。傳統的構造語法看起來更像函式聲明。必須採取措
施以使編譯器不把對象構造誤認為函式聲明。只有集合類型和POD類型能用集合初
始化器初始化(用SomeType var = {/*stuff*/};).
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}; //Note the lack of explicit type.
}
統一的初始化形式不會取代構造器語法。某些情況下仍然需要構造器語法。如果一
個類具有初始化列表構造器(TypeName(initializer_list);),,那么只
要初始化列表符合該構造器的類型,初始化列表構造將優先於其他構造形式。
C++0x版本的std::vector將擁有匹配與模板參數的初始化列表構造器。這就意味著
下面這段代碼:
std::vector theVec{4};
這段代碼將調用初始化列表構造器,而不會調用std::vector中接受單個長度參數
並創建相應長度的vector的構造器。為了調用後一個構造器,用戶需要直接使用標
準構造器語法。