左值、右值
右值引用的意义
移动语义
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); }
std::move 和 std::forward 的区别
- std::move 不移动(move)任何东西,也不保证它执行转换的对象可以被移动,它只执行到右值的无条件的转换。
- std::forward也不转发(forward)任何东西,只有当它的参数被绑定到一个右值时,才将参数转换为右值。
- 一个是无条件的,另一个是有条件的,所以有不同的应用场景。
References: