左值、右值
右值引用的意義
移動語義
class A{ public: A(): size(0), data(nullptr){} A(size_t size): size(size){ data = new int[size]; } A(const A& rhs){ size = rhs.size; data = new int[size]; for(int i=0; i<size; ++i){ data[i] = rhs.data[i]; } cout << "Copy constructor" << endl; } A& operator=(const A& rhs){ if(this == &rhs){ return *this; } delete[] data; size = rhs.size; data = new int[size]; for(int i=0; i<size; ++i){ data[i] = rhs.data[i]; } cout << "Copy assignment" << endl; return *this; } ~A(){ delete[] data; } private: int *data; size_t size; };
A(A&& rhs){ data = rhs.data; size = rhs.size; rhs.size = 0; rhs.data = nullptr; cout << "Move constructor" << endl; }
於是通過std::move將源資料轉化為右值後就會呼叫相應的移動建構函式。
int main(){ A a1 = A(10); A a2(std::move(a1)); }
完美轉發
首先看第一個例子:
#include<iostream> using namespace std; int main(){ int x = 5; int& left = x; int&& right = std::move(x); //cout << &left << endl << &right << endl; }
在上面的程式碼中,被宣告的左值引用和右值引用都是左值,是可以輸出它們的地址的。
然後看二個例子:
#include<iostream> using namespace std; void func(int& x){ cout << "左值" <<endl; } void func(int&& x){ cout << "右值" <<endl; } void test(int&& x){ func(x); } int main(){ test(5); }
在上面的程式碼中,最後的輸出是左值,雖然傳給 test 函式的是一個右值,然而在呼叫 func 時,使用的是 x,引數有了名稱,聯絡第一個例子,此時 x 是一個左值。
如果想要最後的輸出是右值,需要使用 std::forward,而 std::forward 中涉及到了引用摺疊。
引用摺疊
對於T&、T&&來說,其引用型別未定,可能是左值引用,也可能是右值引用,需要視傳入的實參而定。
T& | & | T& | 左值引用的左值引用摺疊為左值引用 |
T& | && | T& | 右值引用的左值引用摺疊為左值引用 |
T&& | & | T& | 左值引用的右值引用摺疊為左值引用 |
T&& | && | T&& | 右值引用的右值引用摺疊為右值引用 |
簡而言之,只有兩者均為右值引用時,最後的引用型別才為右值引用,否則均為左值引用。
原始碼分析
remove_reference 原始碼
template<typename _Tp> struct remove_reference { typedef _Tp type; }; // 特化版本 template<typename _Tp> struct remove_reference<_Tp&> { typedef _Tp type; }; template<typename _Tp> struct remove_reference<_Tp&&> { typedef _Tp type; };
std::forward 原始碼
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); }
根據上述原始碼,首先通過remove_reference獲取 _Tp 的型別 type,然後宣告左值引用變數 __t 。
根據 _Tp 的不同,_Tp 會發生引用摺疊:
- 當 _Tp 為左值引用時,_Tp摺疊為左值引用。
- 當 _Tp 為右值引用時,_Tp摺疊為右值引用。
可以發現當 std::forward 的輸入是左值引用時,輸出也是左值引用;輸入是右值引用時,輸出也是右值引用。
在下面的程式碼中,使用了 std::forward 之後就會輸出右值。
#include<iostream> using namespace std; void func(int& x){ cout << "左值" <<endl; } void func(int&& x){ cout << "右值" <<endl; } void test(int&& x){ func(std::forward<int>(x)); } int main(){ test(5); }
References: