这是四个概念是在c++中比较容易混淆的概念,这里简单梳理一下四者的区别、应用场景。
左值与右值
左值全称是左值表达式,简单的理解就是它可以作为赋值运算符的左边的表达式或值。右值则是临时变量或常量值,不能通过变量名直接再次使用它。通常是将右值赋值给左值,也就是给一个变量名,然后使用这个左值。
还有其它几个版本的理解:
左值就是有名字的变量(对象),可以被赋值,可以在多条语句中使用,而右值呢,就是临时变量(对象),没有名字,只能在一条语句中出现,不能被赋值。
能取地址的是左值,不能取地址的是右值
在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)
在理解C++11的右值前,先看看C++98中右值的概念:C++98中右值是纯右值,纯右值指的是临时变量值、不跟对象关联的字面量值。临时变量指的是非引用返回的函数返回值、表达式等,例如函数int func()的返回值,表达式a+b;不跟对象关联的字面量值,例如true,2,”C”等。
C++11对C++98中的右值进行了扩充。在C++11中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在C++98标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是C++11新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用T&&的函数返回值、std::move的返回值,或者转换为T&&的类型转换函数的返回值。
将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
我的简单理解:
- 能放在赋值符号左边的是左值,不能放在赋值符号左边的是右值
// i是左值,10是右值
int i = 10;
// j 是左值, i*10 是右值,因为它不能写在赋值运算符左边
int j = i * 10;
从上面的例子还可看到,右值是如何转换成左值的,即赋值给个变量名,这个变量就是左值。如果想从左值转化成右值?
int a = 10; // a 是左值
int &&b = std::move(a); // std::move(a);整体是右值
左值引用与右值引用
左值引用即左值的引用,右值引用即右值的引用。
// 左值引用的基本语法
type & 引用名 = 左值表达式;
// 右值引用的基本语法
type && 引用名 = 右值表达式;
还有万能的引用,即常量左值引用即const type &
,能引用所有的类型。例如以下代码是不合法的
void print(int &a){ // 左值引用
cout << a << endl;
}
int main(){
int a=10;
print(a); // ok, a是左值
print(10); // 报错,因为字面量10是右值,不能作为实参传给左值引用形参
}
但是改成const 引用就可以
void print(const int &a){ // 左值引用
cout << a << endl;
}
int main(){
int a=10;
print(a); // ok, a是左值
print(10); // ok
}
左值引用就是别名,在函数传参的时候可以避免拷贝,同样右值引用也可以避免拷贝,具体来说,右值引用是解决以下两个问题
- 临时对象非必要的昂贵的拷贝操作
- 在模板函数中如何按照参数的实际类型进行转发
int getInt(){
int b = 1;
return b;
}
int a = getInt();
在上面的代码中,getInt()
函数返回值其实是个临时变量(假设为c),变量b在出函数时就被销毁了,而c在赋值给a后也会销毁,这里就涉及到两次拷贝了,先b拷贝到c,再c拷贝到a。c这里就是右值,它是不能放在赋值符号左边的。
然后再看下面的代码
int &&a = getInt();
这里就是右值引用,它引用了右值c,使得c不会被销毁,和a的生命周期一样长了。从而避免了c到a的拷贝操作。
需要注意的是,有变量名的右值引用,这个变量本身是左值
void print(int && a){ // 右值引用
cout << a << endl;
}
int main()
{
int &&a = 10; // a是左值, 10是右值,a也是右值引用
print(a); // 报错,因为左值不能作为实参传入右值引用的形参
}
std::move 和 std::forward区别
std::move 就是无条件把变量变成右值引用,std::forward主要用于模板函数这种带类型推导的地方,它会保持变量原本的语义,从而实现完美转发。
void print(int&a){
cout << "lvalue reference!"<< endl;
}
void print(int && a){
cout << "rvalue reference!" << endl;
}
template <class T>
void perfectForward(T&& a){ // 搭配通用引用类型
print(forward<T>(a)); // 会根据a的类型进行转化
}
template <class T>
void badForward(T&&a){
print(a);
}
int main()
{
int &&a = 10;
int b=10;
print(a); // "lvalue reference!"
print(b); // "lvalue reference!"
print(move(a)); // "rvalue reference!"
print(move(b)); // "rvalue reference!"
perfectForward(a); // lvalue refercence,a本身是左值,调用的是对应函数
perfectForward(move(a)); // "rvalue reference!" a经过move后,变成右值,调用对应函数
perfectForward(b); // lvalue refercence b本身是左值,调用对应函数
badForward(a); // "lvalue reference!"
badForward(move(a)); // "lvalue reference!"
badForward(b); // "lvalue reference!"
}
下面的博客都写的不错
- C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
- 条款23:理解std::move和std::forward
- 左值引用和右值引用
- C++之左值、右值、左值引用、右值引用
- https://www.cnblogs.com/qicosmos/p/4283455.html
- https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/