本次主要講c++11中的右值引用,后面還會講到右值引用如何結合std::move優化我們的程序。
c++11增加了一個新的類型,稱作右值引用(R-value reference),標記為T &&,說到右值引用類型之前先要了解什么是左值和右值。
左值具名,對應指定內存域,可訪問;右值不具名,不對應內存域,不可訪問。臨時對像是右值。左值可處于等號左邊,右值只能放在等號右邊。區分表達式的左右值屬性有一個簡便方法:若可對表達式用 & 符取址,則為左值,否則為右值。
1.簡單的賦值語句
如:int i = 0;
在這條語句中,i 是左值,0 是臨時值,就是右值。在下面的代碼中,i 可以被引用,0 就不可以了。立即數都是右值。
2.右值也可以出現在賦值表達式的左邊,但是不能作為賦值的對象,因為右值只在當前語句有效,賦值沒有意義。
如:((i>0) ? i : j) = 1;
在這個例子中,0 作為右值出現在了”=”的左邊。但是賦值對象是 i 或者 j,都是左值。
在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用綁定一個右值,如 :
const int &a = 1;
在這種情況下,右值不能被修改的。但是實際上右值是可以被修改的,既然右值可以被修改,那么就可以實現右值引用。右值引用能夠方便地解決實際工程中的問題。
int && a = 1; //&&為右值引用
&&的特性
實際上T&&并不是一定表示右值引用,它的引用類型是未定的,即可能是左值有可能是右值。看看這個例子:
template<typename T> void f(T&& param);f(10); //10是右值 int x = 10; f(x); //x是左值
從這個例子可以看出,param有時是左值引用,有時是右值引用,它在上面的例子中&&實際上是一個未定的引用類型。這個未定的引用類型被scott meyers稱為universal references(可以認為它是種通用的引用類型),它必須被初始化,它是左值應用還是右值引用取決于它的初始化,如果&&被一個左值初始化的話,它就是一個左值引用;如果它被一個右值初始化的話,它就是一個右值引用。
&&為universal references時的唯一條件是有類型推斷發生。
template<typename T> void f(T&& param); //這里T的類型需要推導,所以&&是一個universal references template<typename T> class Test { ... Test(Test&& rhs); // 已經定義了一個特定的類型, 沒有類型推斷 ... // && 是一個右值引用 };void f(Test&& param); // 已經定義了一個確定的類型, 沒有類型推斷,&& 是一個右值引用
再看一個復雜一點的例子
template<typename T>
void f(std::vector<T>&& param);
這里既有推斷類型T又有確定類型vector,那么這個param到底是什么類型呢?
它是右值引用類型,因為在調用這個函數之前,這個vector<T>中的推斷類型已經確定了,所以到調用f時沒有類型推斷了。
再看看這個例子:
template<typename T> void f(const T&& param);
這個param是universal references嗎?錯,它是右值引用類型,也許會迷糊,T不是推斷類型嗎,怎么會是右值引用類型。其實還有一條規則:universal references僅僅在T&&下發生,任何一點附加條件都會使之失效,而變成一個右值引用。
引用折疊(Reference collapsing)規則:
- 所有的右值引用疊加到右值引用上變成一個右值引用
- 所有的其它引用類型疊加都變成一個左值引用
- 左值或者右值是獨立于它的類型的,也就是說一個右值引用類型的左值是合法的。
int&& var1 = x; // var1 is of type int&& (no use of auto here) auto&& var2 = var1; // var2 is of type int& ,var2的類型是universal references(有類型推導)
var1的類型是一個左值類型,但var1本身是一個左值;
var1是一個左值,根據引用折疊規則,var2是一個int&
?
int w1, w2; auto&& v1 = w1; decltype(w1)&& v2 = w2;
v1是一個universal reference,它被一個左值初始化,所以它最終一個左值;
v2是一個右值引用類型,但它被一個左值初始化,一個左值初始化一個右值引用類型是不合法的,所以會編譯報錯。但是如果我希望把一個左值賦給一個右值引用類型該怎么做呢 ,用std::move,decltype(w1)&& v2 = std::move(w2); std::move可以將一個左值轉換成右值,關于std::move將在下一篇博文中介紹。
&&的總結:
- 左值和右值是獨立于它們的類型的,一個左值的類型有可能是右值引用類型。
- T&&是一個未定的引用類型,它可能是左值引用也可能是右值引用類型,取決于初始化的值類型。
- &&成為未定的引用類型的唯一條件是:T&&且發生類型推斷。
- 所有的右值引用疊加到右值引用上變成一個右值引用,其它引用折疊都為左值引用。
如果想更詳細了解&&,可以參考scott-meyers這個文章:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
右值引用優化性能,避免深拷貝
右值引用是用來支持轉移語義的。轉移語義可以將資源 ( 堆,系統對象等 ) 從一個對象轉移到另一個對象,這樣能夠減少不必要的臨時對象的創建、拷貝以及銷毀,能夠大幅度提高 C++ 應用程序的性能。消除了臨時對象的維護 ( 創建和銷毀 ) 對性能的影響。
以一個簡單的 string 類為示例,實現拷貝構造函數和拷貝賦值操作符。
class MyString { private: char* m_data; size_t m_len; void copy_data(const char *s) { m_data = new char[m_len+1]; memcpy(_data, s, m_len); m_data[_len] = '\0'; } public: MyString() { m_data = NULL; m_len = 0; } MyString(const char* p) { m_len = strlen (p); copy_data(p); } MyString(const MyString& str) { m_len = str.m_len; copy_data(str.m_data); std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl; } MyString& operator=(const MyString& str) { if (this != &str) { m_len = str.m_len; copy_data(str._data); } std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl; return *this; } virtual ~MyString() { if (m_data) free(m_data); } };
void test() { MyString a; a = MyString("Hello"); std::vector<MyString> vec; vec.push_back(MyString("World")); }
實現了調用拷貝構造函數的操作和拷貝賦值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是臨時對象,也就是右值。雖然它們是臨時的,但程序仍然調用了拷貝構造和拷貝賦值,造成了沒有意義的資源申請和釋放的操作。如果能夠直接使用臨時對象已經申請的資源,既能節省資源,有能節省資源申請和釋放的時間。這正是定義轉移語義的目的。
用c++11的右值引用來定義這兩個函數
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; }
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; }
有了右值引用和轉移語義,我們在設計和實現類時,對于需要動態申請大量資源的類,應該設計右值引用的拷貝構造函數和賦值函數,以提高應用程序的效率。
c++11 boost技術交流群:296561497,歡迎大家來交流技術。