封面图片由 "wu_wythe" 黑色油墨 原创并友情赞助,特此感谢。
C++引进右值的概念,是为了明确临时变量的类型,在此基础减少反复调用构造函数和析构函数带来的性能损失。
区分左值、右值的意义
没有右值概念时,临时变量的值属性是十分模糊的,它可以被 const type&引用,但它本身并不是常量,比如临时变量可以调用成员函数改变自己。有了右值概念后,临时变量才明确了值属性:右值。
如何区分
左值:具名变量,能取地址
右值:匿名变量,不能取地址
特例:
字符串字面值(string literal)属于特殊情况,匿名变量,但可以取地址,属于const char []类型 其他字面值常量属于右值(注意不是右值常量 !)
表达式属性
c++表达式有两个属性:类型(type)和 值属性(value category)。
左值引用和右值引用属于类型 左值和右值属于值属性
int k = 5;//k类型:int,值属性:左值int& j = 5; //j类型:int&,值属性:左值int&& i = 5; //i类型:int&&,值属性:左值
上述三个表达式,除了类型不同外,在后续代码中使用并没有别的差别
decltype(k); //intdecltype(j) ;//int&decltype(i) ;// int&&
你可能会奇怪,我为什么需要这么麻烦的表达,直接用k这样的传值方式不好么,这一切都是为了万恶的效率。
这对普通人来说,效率根本不重要,我们只需要是要了解它即可。函数参数(parameter)的值属性可以区分实参(argument)的类型,从而针对左右值分别编写构造赋值函数,提升程序效率。
// void fun(int parameter);//编译器不允许它与下面两个函数共存void fun(int& parameter); //实参类型为左值时,调用它void fun(int&& parameter);//实参类型为右值时,调用它
fun(j);//调用左值引用版本,如果不存在左值版本,会出错fun(5);//调用右值引用版本,如果不存在右值版本,会出错
请注意如下调用,全部匹配void fun(int& parameter)
fun(k);//arugment(实参)是左值fun(j);//arugment(实参)是左值fun(i);//arugment(实参)是左值
如何判断表达式的左右值属性
所有的值必属于左值、将亡值、纯右值三者之一。
纯右值:函数by value返回的局部变量 运算表达式产生的临时变量 原始字面量(string literal除外)和lambda。
将亡值:强制转换为Type&& 右值对象的成员变量
在函数argument——parameter匹配时,将亡值和纯右值都看做右值。
如何匹配
我们注意到f(j)和f(5)的调用,如果不存在对于函数版本,他们的调用就会出错。(为什么你写的程序不会出错,因为你使用的是void fun(int parameter))
函数形参可接受的实参值属性
parameter’s type | argument’s value category | |
Type& | lval | 左值 |
const Type& | clval,val,crval,rval | 全部类型 |
Type&& | rval | 右值 |
const Type&& | crval,rval | 右值和右值常量 |
注意Type是指某个具体类型,不是函数模板类型参数。
右值常量实参匹配到右值常量引用版本的函数,如果不存在此版本,则匹配到左值常引用的函数版本。右值实参的匹配顺序为Type&&→const Type&&→const Type&&。
右值引用是指 他所引用的变量是右值,(右值二字)限定的是别人,而右值引用自身是左值;const引用限定的是自己修改对象的能力,它出现在接口中只是为了扩大函数可接受的参数范围。
上述例子中的整数“5”是右值,而非右值常量。
后面讲陆续总结模板推导和函数返回值优化,move与完美转发。欢迎大家私信探讨。
--
参考资料
[1] Effective Modern C++ [2] 左值和右值https://blog.csdn.net/linuxheik/article/details/78929128