C++右值引用
左值、右值
右值引用的意义
移动语义
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:

浙公网安备 33010602011771号