背景

lvalue(左值)、rvalue(右值)这些术语来自C语言(当然,C语言的术语习惯也可能来自更早的语言,Gemfield就不追溯了)。在C语言中,lvalue和rvalue中的l和r是left和right,分别代表着赋值表达式(等号)的左边和右边。并且:

  • 出现在等号左边的必须是lvalue;
  • rvalue只能出现在等号的右边。
g1 = g2

其中等号左边的g1必须是lvalue,g2可以是lvalue或者rvalue。但其实把lvalue中的l看成是location就容易理解,就是lvalue是有直接的memory location的,而rvalue没有。比如:

int gem;
gem = 7030;

gem是lvalue,我们可以通过&gem来获得gem的memory location;而7030则是rvalue,我们无法获得它的内存地址。

C++的到来

C++语言的一些特性就改变了前述规则。

1,class类型

上述的规则在C++中就变得不准确了,首先就是class类型。在前述小节中我们得知,rvalue是没有memory的(备注:对于一些大的复杂的rvalue,其实也是隐式占用memory的,但对于外部用户来说,假定它没有占用),但当到了C++的class时代后,class类型的rvalue是会占用memory的:


struct Gemfield{int x;int y;
};
Gemfield f(){return {7,19};
}

函数f返回的依然是rvalue,但此时的rvalue已经是个默认的class类型的对象,它是占用内存的(不然为啥会有移动语义,好了这里先不展开)。

2,const

有了const后,不是所有的左值都可以出现在赋值表达式的左边了。比如:

const int gem = 7030;
gem = 1002; //error: assignment of read-only variable ‘gem'

gem是lvalue,但不能出现在等号左边了。

3,新的引用规则

C++新增了一条规则:reference可以绑定到lvalue,但只有reference to const 可以绑定到rvalue。具体来说:

  • reference to T的引用只能绑定到T类型的lvalue上,比如下面的代码就是错误的(7030是rvalue):
int& gem = 7030; //错误
  • reference to const T却不一定非得绑定到T类型的lvalue上,只要表达式产生的值可以转换成T类型即可。比如下面两句代码都正确,因为7030这个表达式产生的值(也即7030)可以转换为int或者double,于是c++编译器就创建了一个临时对象(第一句是临时int,第二句是临时double对象),然后将gem引用绑定到该临时对象上:
const int& gem = 7030;
const double& gem = 7030;

这样的行为正是函数形参经常为reference to const T的重要原因。reference to non-const 形参只接收non-const的实参;而reference to const的形参可以接收const或者non-const的实参,不论接收哪种,reference to const都会产生一个non-modifiable lvalue。

Morden C++的到来

Morden C++,也就是C++11,更具体来说就是ISO/IEC 14882:2011,带来了新的value分类——其中最重要的一点便是带来了右值引用(rvalue reference)。这就意味着:

  • 传统C++时代的引用,在morden c++(C++11及以后)中成为了左值引用(lvalue reference);
  • Morden C++新引入的右值引用(rvalue reference)开创了一个全新的领域:移动语义

左值引用使用&操作符,而右值引用使用&&操作符,比如:

int&& gem = 7030;

并且右值引用既可以作为函数形参,也可以作为函数返回值:

Gemfield&& f(int && g);

且morden c++又为reference增加了一条规则:rvalue reference只能绑定到rvalue上,甚至rvalue reference to const也只能绑定到rvalue上

int gem = 7030;
int&& r = gem; //error: cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’
const int&& rr = gem; //error: cannot bind rvalue reference of type ‘const int&&’ to lvalue of type ‘int’

继续说回移动语义(move semantics)。C++11在移动语义的基础上,将value类别重构为如下几类:

  • 有名字,就是glvalue(generalized lvalue,包含lvalue和xvalue);
  • 有名字,且不能被move,就是lvalue;
  • 有名字,且可以被move,就是xvalue(eXpiring value,走到生命周期尽头的value,因此可以被move);
  • 没有名字,且可以被移动,则是prvalue(pure rvalue)。
value类别 是否有名字 是否可move 备注
glvalue generalized lvalue = lvalue + xvalue
lvalue
xvalue eXpiring value
prvalue pure rvalue
rvalue rvalue = xvalue + prvalue

其中lvalue、prvalue、xvalue是最基础的分类,而glvalue、rvalue是混合的:

  • glvalue = lvalue + xvalue
  • rvalue = prvalue + xvalue

1,lvalue

下列表达式是lvalue表达式:

  • 变量的名字、函数的名字(注意不是函数调用)、数据成员的名字;
  • 函数调用,且其返回值类型为lvalue reference,比如:
std::cout<<"gemfield";
str1 = str2;
++it;
  • 内置的赋值表达式,比如:
a = b;
a += b;
a %=b;
  • 内置的pre-increment、pre-decrement表达式(注意:是pre而不是post):
++a;
--a;
  • 内置的间接寻址运算符(indirection expression):
*p;
  • 内置的下标运算符(subscript expression):
a[n];
p[n];
  • 类的数据成员:
a.m;
p->m;
  • 逗号表达式(comma expression):
g,e; //e需要为lvalue
  • 三元条件表达式:
g ? e : m; //e和m需要是lvalue
  • 字符串:
"gemfield";
  • cast表达式,且cast为lvalue reference type:
static_cast<int&>(x)

2,prvalue

下列表达式是prvalue表达式:

  • 字符(注意不是字符串):比如'g'、'e'、true、nullptr、35;
  • 函数调用,或重载运算符表达式,且返回值的类型为非引用,比如:
str.substr(1,2);
str1 + str2;
it++;
  • 内置的post-increment和post-decrement表达式(注意不是pre):
a++;
a--;//想一下为什么下面的表达式是错的
(gem++)++ //error: lvalue required as increment operand.
(++gem)++ //正确
++(gem++) //error: lvalue required as increment operand
  • 内置的算术运算表达式:a + b、a % b、 a & b、 a << b
  • 内置的逻辑表达式:a && b、 a || b、 !a
  • 内置的比较表达式:a < b、a == b、 a >= b
  • 内置的取地址表达式:&a
  • 类的非static成员函数:a.m、p->m
  • 逗号表达式:a,b ,且b为rvalue;
  • cast表达式,且cast为非引用类型,比如:
static_cast<double>(x);
std::string{};
(int)42;
  • this指针;
  • enumerator;
  • lambda表达式:
[](int x){return x + 7030;};
  • requires表达式:
requires (T i){typename T::type;};

3,xvalue

下列表达式是xvalue表达式:

  • 函数调用,或者重载运算符表达式,且返回值类型为rvalue reference,比如:std::move(x);
  • rvalue对象的non-reference类型的非静态数据成员:a.m;
  • cast表达式,且cast为右值引用,比如:
static_cast<char&&>(x);
  • temporary materialization时生成的临时对象:
struct S { int m; };
int i = S().m; // C++17标准:要访问对象的成员时,期望一个glvalue// S() prvalue 被转换为 xvalue
  • temporary materialization还有其它很多情况,比如右值引用绑定到右值上时会创建xvalue。

再谈移动语义

先看一段代码:

string s1, s2, s3;
s1 = s2; //调用拷贝赋值
s1 = s2 + s3; //调用移动赋值,因为s2 + s3产生prvalue,前面讲过了

然后s2 + s3这个prvalue传递给move assignment operator的右值引用形参。假设move assignment operator的实现如下所示:

string& string::operator=(string&& rhs) {...string temp(rhs);...
}

那么代码中的string temp(rhs)是调用string类的拷贝构造函数呢,还是移动构造函数呢?答案是拷贝构造,因为rhs这个时候不是rvalue:rhs有名字,且预期在函数结束前它的生命都还在——这是lvalue!

但是有时候,我们编程时,就是想要move一个lvalue——因为程序员了解自己的意图(显然编译器不了解),比如:

template <typename T>
void swap(T& a, T& b){T temp(a);a = b;b = temp;
}

在函数第一行的T temp(a)中,编译器认为a还将继续有生命,但程序员知道过了这行后,a原有的资源已经不需要了(因为要用b填充了),那怎么在这一行调用移动构造而不是拷贝构造呢?这就相当于,如何让编译器知道a不再是个lvalue,而是个要过期的value——也即eXpiring value——也即xvalue——也即一种rvalue呢?那我们就需要做个转换。

前文讲过:“xvalue表达式的一种情况是:函数调用,或者重载运算符表达式,且返回值类型为rvalue reference,比如:std::move(x)”,这就是std::move的意义,可以将lvalue转换为xvalue(一种rvalue)。

总结

现在,C++不止有lvalue和rvalue了,而是:

C++的左值(lvalue)和右值(rvalue)相关推荐

  1. C语言的左值(lvalue)和右值(rvalue)的含义是什么?

    C语言的左支(lvalue)和右值(rvalue)的含义是什么? (1)左值就是一个可被存储的单元,右值就是一个可被读取的数据.      (2)左值必须是一个被明确了的内存存储单元,可以用来被赋值: ...

  2. c语言中字符串关于左值,关于左值lvalue和右值rvalue的一点理解

    发现很多朋友对"lvalue"和"rvalue"理解有误,我先谈谈自己对此的一些理解,并期望能够引起更多朋友的广泛讨论.也算起到抛砖引玉的作用吧.引用:注:这里 ...

  3. c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值

    C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...

  4. C++左值、右值、左值引用、右值引用

    左值(lvalue)和右值(rvalue) 左值(lvalue):locator value,存储在内存中.有明确的的地址(可寻址)的数据 能够取地址,有名字的值就是左值 //左值引用 int a=1 ...

  5. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  6. 的引用_左值、右值、左值引用、右值引用

    [导读]:本文主要详细介绍了左值.右值.左值引用.右值引用以及move.完美转发. 左值和右值 左值(left-values),缩写:lvalues 右值(right-values),缩写:rvalu ...

  7. java左值与右值问题_[C++11]左值、右值、左值引用、右值引用小结

    左值和右值 左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字. 比如 int a = b + c;,a 就是一个左值, ...

  8. 左值右值,左值引用和右值引用及其用途

    目录 1.左值和右值 2.引用 (1)左值引用 (2)右值引用 3.左值引用的用途 (1)作为复杂名称变量的别名 (2)用于rangeFor循环 (3)避免复制大的对象 (4)参与函数中的参数传递 4 ...

  9. [c++]-c++中的左值和右值、左值引用和右值引用、万能引用和引用折叠及完美转发

    1.左值和右值 1.1左值和右值定义 在c++中,左值是一个指向内存的东西,换句话来讲,左值有地址,保存在内存中,右值则为不指向任何地方东西,即不在内存中占有确定位置.一般来说,右值是暂时和短暂的,而 ...

最新文章

  1. 解释个人计算机与多用户系统之间的区别,计算机导论问答题答案
  2. 某产品经理炫耀:3年跳槽3次,月薪从8k涨到38k,跳槽涨薪最快!
  3. 有关函数模板和类模板的说法
  4. Entity Framework 4.1 : 贪婪加载和延迟加载
  5. 自我训练——时间控制能力(四)
  6. 2 RepMLP:卷积重参数化为全连接层进行图像识别 (Arxiv)
  7. 全球互联网领域第一人!马云获福布斯终身成就奖
  8. 马尔可夫链的扩展 贝叶斯网络 (Bayesian Networks)
  9. springmvc配置拦截器
  10. linux运行关关采集器,杰奇小说2.3-自动采集-关关采集器高级版
  11. 【智慧城市】智能照明系统解决方案
  12. caffe框架学习(layer)
  13. Python爬虫之初窥Scrapy
  14. 阿里云STMP邮箱验证
  15. 信息系统项目管理重点:信息化发展原则和方向
  16. python+html实现前后端数据交互界面显示
  17. 个人面试问答题知识库(一)百面机器学习篇
  18. matlab 毕业答辩,MATLAB的答辩.ppt
  19. python简笔画程序_用python设计程序输生日判断星座,及星座简笔画。
  20. 如何把一组计算机做成云,旧电脑如何变成云电脑?进来了解下云电脑

热门文章

  1. OpenCV学习20-直方图反向投影
  2. 区块链中的密码朋克是什么?
  3. Spring Data JPA - 参考文档-3
  4. nginx 源码安装并开启gzip静态压缩
  5. 1.程序员仪式与注释
  6. 声品质粗糙度matlab,声品质的应用分析方法
  7. 智能优化算法:正余弦优化算法-附代码
  8. 个性化、定制化消费成传统企业发展“新命题”
  9. MySQL从删库到跑路(10):case when——国色天香、倾国倾城,给漂亮小姐姐分个类
  10. BT5安装qq2012