C++11 新特性之右值引用和轉移建構函式
阿新 • • 發佈:2019-01-03
問題背景
- #include <iostream>
- usingnamespace std;
- vector<int> doubleValues (const vector<int>& v)
- {
- vector<int> new_values( v.size() );
- for (auto itr = new_values.begin(), end_itr = new_values.end(); itr != end_itr; ++itr )
- {
-
new_values.push_back( 2 * *itr );
- }
- return new_values;
- }
- int main()
- {
- vector<int> v;
- for ( int i = 0; i < 100; i++ )
- {
- v.push_back( i );
- }
- v = doubleValues( v );
- }
先來分析一下上述程式碼的執行過程。
- vector<int> v;
-
for ( int i = 0; i < 100; i++ )
- {
- v.push_back( i );
- }
以上5行語句在棧上新建了一個vector的例項,並在裡面放了100個數。
- v = doubleValues( v )
但是
- vector<int> new_values( v.size() );
- v = doubleValues( v );
函式執行完之後,new_values中放了翻倍之後的數值,作為函式的返回值返回。但是注意,這個時候doubleValue(v)的呼叫已經結束。開始執行 = 的語義。
賦值的過程實際上是將返回的vector<int>複製一份放入新的記憶體空間,然後改變v的地址,讓v指向這篇記憶體空間。總的來說,我們剛才新建的那個vector又被複制了一遍。
但我們其實希望v能直接得到函式中複製好的那個vector。在C++11之前,我們只能通過傳遞指標來實現這個目的。但是指標用多了非常不爽。我們希望有更簡單的方法。這就是我們為什麼要引入右值引用和轉移建構函式的原因。
左值和右值
在說明左值的定義之前,我們可以先看幾個左值的例子。- int a;
- a = 1; // here, a is an lvalue
- int x;
- int& getRef ()
- {
- return x;
- }
- getRef() = 4;
- int x;
- int getVal ()
- {
- return x;
- }
- getVal();
下面的語句就是錯的。
- getVal() = 1;//compilation error
右值引用
在C++11中,你可以使用const的左值引用來繫結一個右值,比如說:- constint& val = getVal();//right
- int& val = getVal();//error
因為左值引用並不是左值,並沒有建立一片穩定的記憶體空間,所以如果不是const的話你就可以對它的內容進行修改,而右值又不能進行賦值,所以就會出錯。因此只能用const的左值引用來繫結一個右值。 在C++11中,我們可以顯示地使用“右值引用”來繫結一個右值,語法是"&&"。因為指定了是右值引用,所以無論是否const都是正確的。
- const string&& name = getName(); // ok
- string&& name = getName(); // also ok
有了這個功能,我們就可以對原來的左值引用的函式進行過載,過載的函式引數使用右值引用。比如下面這個例子:
- printReference (const String& str)
- {
- cout << str;
- }
- printReference (String&& str)
- {
- cout << str;
- }
- string me( "alex" );
- printReference( me ); // 呼叫第一函式,引數為左值常量引用
- printReference( getName() ); 呼叫第二個函式,引數為右值引用。
好了,現在我們知道C++11可以進行顯示的右值引用了。但是我們如果用它來解決一開始那個複製的問題呢? 這就要引入與此相關的另一個新特性,轉移建構函式和轉移賦值運算子
轉移建構函式和轉移賦值運算子
假設我們定義了一個ArrayWrapper的類,這個類對陣列進行了封裝。- class ArrayWrapper
- {
- public:
- ArrayWrapper (int n)
- : _p_vals( newint[ n ] )
- , _size( n )
- {}
- // copy constructor
- ArrayWrapper (const ArrayWrapper& other)
- : _p_vals( newint[ other._size ] )
- , _size( other._size )
- {
- for ( int i = 0; i < _size; ++i )
- {
- _p_vals[ i ] = other._p_vals[ i ];
- }
- }
- ~ArrayWrapper ()
- {
- delete [] _p_vals;
- }
- private:
- int *_p_vals;
- int _size;
- };
我們可以看到,這個類的拷貝建構函式顯示新建了一片記憶體空間,然後又對傳進來的左值引用進行了複製。 如果傳進來的實際引數是一個右值(馬上就銷燬),我們自然希望能夠繼續使用這個右值的空間,這樣可以節省申請空間和複製的時間。 我們可以使用轉移建構函式實現這個功能:
- class ArrayWrapper
- {
- public:
- // default constructor produces a moderately sized array
- ArrayWrapper ()
- : _p_vals( newint[ 64 ] )
- , _size( 64 )
- {}
- ArrayWrapper (int n)
- : _p_vals( newint[ n ] )
- , _size( n )
- {}
- // move constructor
- ArrayWrapper (ArrayWrapper&& other)
- : _p_vals( other._p_vals )
- , _size( other._size )
- {
- other._p_vals = NULL;
- }