C++右值引用
作者:互联网
左值、右值
1 左值是表达式结束后依然存在的持久对象。
2 右值是表达式结束后不再存在的临时对象。
简单来说,能取地址的是左值,否则就是右值。
右值引用的意义
实现移动语义和完美转发。
移动语义
C++11的右值引用和std::move可以实现移动语义,通过减少拷贝操作提升效率。
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:
标签:std,右值,int,左值,C++,引用,size 来源: https://www.cnblogs.com/zyb993963526/p/15933775.html