VS2013出來了,對於C++來講,最大的改變莫過於對於C++11新特性的支持,在網上搜了一下C++11的介紹,發現這篇文章很是不錯,分享給你們同時本身做爲存檔。程序員
原文地址:http://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/函數
C++ 的新標準 C++11 已經發布一段時間了。本文介紹了新標準中的一個特性,右值引用和轉移語義。這個特性可以使代碼更加簡潔高效。性能
新特性的目的
右值引用 (Rvalue Referene) 是 C++ 新標準 (C++11, 11 表明 2011 年 ) 中引入的新特性 , 它實現了轉移語義 (Move Sementics) 和精確傳遞 (Perfect Forwarding)。它的主要目的有兩個方面:this
- 消除兩個對象交互時沒必要要的對象拷貝,節省運算存儲資源,提升效率。
- 可以更簡潔明確地定義泛型函數。
左值與右值的定義
C++( 包括 C) 中全部的表達式和變量要麼是左值,要麼是右值。通俗的左值的定義就是非臨時對象,那些能夠在多條語句中使用的對象。全部的變量都知足這個定義,在多條代碼中均可以使用,都是左值。右值是指臨時的對象,它們只在當前的語句中有效。請看下列示例 :spa
- 簡單的賦值語句
如:int i = 0;在這條語句中,i 是左值,0 是臨時值,就是右值。在下面的代碼中,i 能夠被引用,0 就不能夠了。當即數都是右值。翻譯
- 右值也能夠出如今賦值表達式的左邊,可是不能做爲賦值的對象,由於右值只在當前語句有效,賦值沒有意義。
如:
((i>0) ? i : j) = 1;
設計在這個例子中,0 做爲右值出如今了」=」的左邊。可是賦值對象是 i 或者 j,都是左值。code
在 C++11 以前,右值是不能被引用的,最大限度就是用常量引用綁定一個右值,如 :對象
const int &a = 1;在這種狀況下,右值不能被修改的。可是實際上右值是能夠被修改的,如 :blog
T().set().get();T 是一個類,set 是一個函數爲 T 中的一個變量賦值,get 用來取出這個變量的值。在這句中,T() 生成一個臨時對象,就是右值,set() 修改了變量的值,也就修改了這個右值。
既然右值能夠被修改,那麼就能夠實現右值引用。右值引用可以方便地解決實際工程中的問題,實現很是有吸引力的解決方案。
左值和右值的語法符號
左值的聲明符號爲」&」, 爲了和左值區分,右值的聲明符號爲」&&」。
示例程序 :
void process_value(int& i) { std::cout << "LValue processed: " << i << std::endl; } void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; } int main() { int a = 0; process_value(a); process_value(1); }
運行結果 :
LValue processed: 0 RValue processed: 1Process_value 函數被重載,分別接受左值和右值。由輸出結果能夠看出,臨時對象是做爲右值處理的。
可是若是臨時對象經過一個接受右值的函數傳遞給另外一個函數時,就會變成左值,由於這個臨時對象在傳遞過程當中,變成了命名對象。
示例程序 :
void process_value(int& i) { std::cout << "LValue processed: " << i << std::endl; } void process_value(int&& i) { std::cout << "RValue processed: " << i << std::endl; } void forward_value(int&& i) { process_value(i); } int main() { int a = 0; process_value(a); process_value(1); forward_value(2); }
運行結果 :
LValue processed: 0 RValue processed: 1 LValue processed: 2雖然 2 這個當即數在函數 forward_value 接收時是右值,但到了 process_value 接收時,變成了左值。
轉移語義的定義
右值引用是用來支持轉移語義的。轉移語義能夠將資源 ( 堆,系統對象等 ) 從一個對象轉移到另外一個對象,這樣可以減小沒必要要的臨時對象的建立、拷貝以及銷燬,可以大幅度提升 C++ 應用程序的性能。臨時對象的維護 ( 建立和銷燬 ) 對性能有嚴重影響。
轉移語義是和拷貝語義相對的,能夠類比文件的剪切與拷貝,當咱們將文件從一個目錄拷貝到另外一個目錄時,速度比剪切慢不少。
經過轉移語義,臨時對象中的資源可以轉移其它的對象裏。
在現有的 C++ 機制中,咱們能夠定義拷貝構造函數和賦值函數。要實現轉移語義,須要定義轉移構造函數,還能夠定義轉移賦值操做符。對於右值的拷貝和賦值會調用轉移構造函數和轉移賦值操做符。若是轉移構造函數和轉移拷貝操做符沒有定義,那麼就遵循現有的機制,拷貝構造函數和賦值操做符會被調用。
普通的函數和操做符也能夠利用右值引用操做符實現轉移語義。
實現轉移構造函數和轉移賦值函數
以一個簡單的 string 類爲示例,實現拷貝構造函數和拷貝賦值操做符。
示例程序 :
class MyString { private: char* _data; size_t _len; void _init_data(const char *s) { _data = new char[_len+1]; memcpy(_data, s, _len); _data[_len] = '\0'; } public: MyString() { _data = NULL; _len = 0; } MyString(const char* p) { _len = strlen (p); _init_data(p); } MyString(const MyString& str) { _len = str._len; _init_data(str._data); std::cout << "Copy Constructor is called! source: " << str._data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { _len = str._len; _init_data(str._data); } std::cout << "Copy Assignment is called! source: " << str._data << std::endl; return *this; } virtual ~MyString() { if (_data) free(_data); } }; int main() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World")); }
運行結果 :
Copy Assignment is called! source: Hello Copy Constructor is called! source: World這個 string 類已經基本知足咱們演示的須要。在 main 函數中,實現了調用拷貝構造函數的操做和拷貝賦值操做符的操做。MyString(「Hello」) 和 MyString(「World」) 都是臨時對象,也就是右值。雖然它們是臨時的,但程序仍然調用了拷貝構造和拷貝賦值,形成了沒有意義的資源申請和釋放的操做。若是可以直接使用臨時對象已經申請的資源,既能節省資源,有能節省資源申請和釋放的時間。這正是定義轉移語義的目的。
咱們先定義轉移構造函數。
MyString(MyString&& str) { std::cout << "Move Constructor is called! source: " << str._data << std::endl; _len = str._len; _data = str._data; str._len = 0; str._data = NULL; }
和拷貝構造函數相似,有幾點須要注意:
1. 參數(右值)的符號必須是右值引用符號,即「&&」。
2. 參數(右值)不能夠是常量,由於咱們須要修改右值。
3. 參數(右值)的資源連接和標記必須修改。不然,右值的析構函數就會釋放資源。轉移到新對象的資源也就無效了。
如今咱們定義轉移賦值操做符。
MyString& operator=(MyString&& str) { std::cout << "Move Assignment is called! source: " << str._data << std::endl; if (this != &str) { _len = str._len; _data = str._data; str._len = 0; str._data = NULL; } return *this; }
這裏須要注意的問題和轉移構造函數是同樣的。
增長了轉移構造函數和轉移複製操做符後,咱們的程序運行結果爲 :
Move Assignment is called! source: Hello Move Constructor is called! source: World由此看出,編譯器區分了左值和右值,對右值調用了轉移構造函數和轉移賦值操做符。節省了資源,提升了程序運行的效率。
有了右值引用和轉移語義,咱們在設計和實現類時,對於須要動態申請大量資源的類,應該設計轉移構造函數和轉移賦值函數,以提升應用程序的效率。
標準庫函數 std::move
既然編譯器只對右值引用才能調用轉移構造函數和轉移賦值函數,而全部命名對象都只能是左值引用,若是已知一個命名對象再也不被使用而想對它調用轉移構造函數和轉移賦值函數,也就是把一個左值引用當作右值引用來使用,怎麼作呢?標準庫提供了函數 std::move,這個函數以很是簡單的方式將左值引用轉換爲右值引用。
示例程序 :
void ProcessValue(int& i) { std::cout << "LValue processed: " << i << std::endl; } void ProcessValue(int&& i) { std::cout << "RValue processed: " << i << std::endl; } int main() { int a = 0; ProcessValue(a); ProcessValue(std::move(a)); }
運行結果 :
LValue processed: 0 RValue processed: 0
std::move
在提升 swap 函數的的性能上很是有幫助,通常來講,swap
函數的通用定義以下:template <class T> swap(T& a, T& b) { T tmp(a); // copy a to tmp a = b; // copy b to a b = tmp; // copy tmp to b }
有了 std::move,swap 函數的定義變爲 :
template <class T> swap(T& a, T& b) { T tmp(std::move(a)); // move a to tmp a = std::move(b); // move b to a b = std::move(tmp); // move tmp to b }
經過 std::move,一個簡單的 swap 函數就避免了 3 次沒必要要的拷貝操做。
精確傳遞 (Perfect Forwarding)
本文采用精確傳遞表達這個意思。」Perfect Forwarding」也被翻譯成完美轉發,精準轉發等,說的都是一個意思。
精確傳遞適用於這樣的場景:須要將一組參數原封不動的傳遞給另外一個函數。
「原封不動」不只僅是參數的值不變,在 C++ 中,除了參數值以外,還有一下兩組屬性:
左值/右值和 const/non-const。 精確傳遞就是在參數傳遞過程當中,全部這些屬性和參數值都不能改變。在泛型函數中,這樣的需求很是廣泛。
下面舉例說明。函數 forward_value 是一個泛型函數,它將一個參數傳遞給另外一個函數 process_value。
forward_value 的定義爲:
template <typename T> void forward_value(const T& val) { process_value(val); } template <typename T> void forward_value(T& val) { process_value(val); }函數 forward_value 爲每個參數必須重載兩種類型,T& 和 const T&,不然,下面四種不一樣類型參數的調用中就不能同時知足 :
int a = 0; const int &b = 1; forward_value(a); // int& forward_value(b); // const int& forward_value(2); // int&對於一個參數就要重載兩次,也就是函數重載的次數和參數的個數是一個正比的關係。這個函數的定義次數對於程序員來講,是很是低效的。咱們看看右值引用如何幫助咱們解決這個問題 :
template <typename T> void forward_value(T&& val) { process_value(val); }只須要定義一次,接受一個右值引用的參數,就可以將全部的參數類型原封不動的傳遞給目標函數。四種不用類型參數的調用都能知足,參數的左右值屬性和 const/non-cosnt 屬性徹底傳遞給目標函數 process_value。這個解決方案不是簡潔優雅嗎?
int a = 0; const int &b = 1; forward_value(a); // int& forward_value(b); // const int& forward_value(2); // int&&C++11 中定義的 T&& 的推導規則爲:
右值實參爲右值引用,左值實參仍然爲左值引用。
一句話,就是參數的屬性不變。這樣也就完美的實現了參數的完整傳遞。
右值引用,表面上看只是增長了一個引用符號,但它對 C++ 軟件設計和類庫的設計有很是大的影響。它既能簡化代碼,又能提升程序運行效率。每個 C++ 軟件設計師和程序員都應該理解並可以應用它。咱們在設計類的時候若是有動態申請的資源,也應該設計轉移構造函數和轉移拷貝函數。在設計類庫時,還應該考慮 std::move 的使用場景並積極使用它。
總結
右值引用和轉移語義是 C++ 新標準中的一個重要特性。每個專業的 C++ 開發人員都應該掌握並應用到實際項目中。在有機會重構代碼時,也應該思考是否能夠應用新也行。在使用以前,須要檢查一下編譯器的支持狀況。