C++右值引用
左值、右值
右值引用的意义
移动语义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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; }; |
1 2 3 4 5 6 7 | A(A&& rhs){ data = rhs.data; size = rhs.size; rhs.size = 0; rhs.data = nullptr ; cout << "Move constructor" << endl; } |
于是通过std::move将源数据转化为右值后就会调用相应的移动构造函数。
1 2 3 4 | int main(){ A a1 = A(10); A a2(std::move(a1)); } |
完美转发
首先看第一个例子:
1 2 3 4 5 6 7 8 9 | #include<iostream> using namespace std; int main(){ int x = 5; int & left = x; int && right = std::move(x); //cout << &left << endl << &right << endl; } |
在上面的代码中,被声明的左值引用和右值引用都是左值,是可以输出它们的地址的。
然后看二个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #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 源码
1 2 3 4 5 6 7 8 9 10 11 12 | 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 源码
1 2 3 4 | 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 之后就会输出右值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #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:
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通