右值引用,std::move和 std::forward

这是四个概念是在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
}

左值引用就是别名,在函数传参的时候可以避免拷贝,同样右值引用也可以避免拷贝,具体来说,右值引用是解决以下两个问题

  1. 临时对象非必要的昂贵的拷贝操作
  2. 在模板函数中如何按照参数的实际类型进行转发
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!"

}

下面的博客都写的不错

  1. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
  2. 条款23:理解std::move和std::forward
  3. 左值引用和右值引用
  4. C++之左值、右值、左值引用、右值引用
  5. https://www.cnblogs.com/qicosmos/p/4283455.html
  6. https://codinfox.github.io/dev/2014/06/03/move-semantic-perfect-forward/
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值