移动语义引发的思考之左值、右值
C++11最广泛的特性是移动语义,移动语义的基础在于区分左值表达式和右值表达式。一般来说,一个对象是右值意味着可以对其实施移动语义,而左值不可以。右值对应的是函数返回的临时对象,而左值对应的是可指涉的对象,指涉途径有:标识符、指针、左值引用等。
常见的疑问有:
(1)什么是左值和右值?
(2)什么是左值表达式、右值表达式?
(3)引用有哪些?什么又是左值引用和右值引用?
1、一种简明的说法是:
在赋值运算法左边的就是左值,右边的就是右值;
表达式求值结果是左值,就是左值表达式,表达式求值结果是右值,就是右值表达式;
- 这种简明的说法不严谨不准确,以下给出详细说明:
表达式是由运算符或运算对象构成的计算式,字面值(literal)和变量(variable)是最简单的表达式,函数返回值也是表达式,而表达式是可以求值的,对表达式求值将得到一个结果(result),这个结果/值/量有两个自带的属性:型别和值类别。类型大家都很容易理解,int、char、reference...,但是值类别可能容易被忽视,而这最容易被忽视和不知的点:值类型正是C++11后划分对象/值/量的根据和基础。
C++11标准中规定复杂,对于值/表达式的解释大概划分如下:
具体说明如下表:
Expression / value |
表达式 / 值 |
Lvalue |
[左值] |
rvalue |
[右值] |
glvalue |
[广义左值/泛左值] |
xvalue |
[将忘值] |
prvalue |
纯右值 |
(1)Prvalue纯右值【纯纯的沙雕】
定义 |
无标识符、不可取地址的表达式/对象,占内存但立即释放 (也叫临时对象) |
纯右值[属于右值] |
|
字面值(字串字面值除外) |
1,‘w’,nullptr,true... |
返回值为非引用的函数调用 |
str.substr(1, 2), str1 + str2, or it++ |
后置自增自减表达式 |
a++, a– |
算术表达式 |
a + b |
逻辑表达式 |
a && b |
比较表达式 |
a > b |
取地址表达式 |
&a |
Lambda表达式 |
... |
(2)Lvalue左值【左盟主】
定义 |
有标识符、可取地址的表达式/对象 |
左值[属于广义左值] |
|
字串字面值 |
“hello world” ... |
变量、函数、数据成员名字 |
int a, fun(), str.m_name |
前置自增自减表达式 |
++a, --a |
返回左值引用的表达式 |
x = 1、cout << ’ ’,... |
注意:
在函数调用时,左值可以绑定到左值引用的参数,如 T&。
一个常量只能绑定到常左值引用,如 const T&。
左值和右值都是针对表达式而言的(也就是说二者不存在严格的区别):
左值是指表达式结束后依然存在的持久对象
右值是指表达式结束时就不再存在的临时对象
C/C++规定:对于对象的引用必须是左值(常量引用除外)
const引用能够绑定到临时对象,
并将临时对象的生命周期由”创建临时对象的完整表达式”提升
至”绑定到的const引用超出作用域” non-const 引用没有这个功能。
示例:
const int& a = 101; //对{常引用可以作用于右值}
int& b = 101; //错{左值引用不可引用右值}
int a;
int &b = a; //对{左值引用可引用左值}
a = 10;
printf(“b = %d\n”,b);
此时b = 10,b是a的引用,就是说b和a的地址相同,对a做改变b也跟着变化。
- Xvalue将亡值【右值引用引出】
定义 |
通过右值引用符号“&&” , 返回右值引用的函数调用表达式, 转换为右值引用的转换函数的调用表达 |
将亡值[右值引用/广义左值] |
|
T&& 函数返回值 |
T&& fun(); |
移动语义返回值 |
std::move、tsatic_cast<X&&>(x) |
具有右值引用型别的左值 |
在C++11中,用左值去初始化对象或为其赋值,会调用拷贝构造函数或拷贝赋值运算符函数来拷贝资源,用右值(纯右值和将亡值)初始化和赋值时,调用移动构造函数或移动赋值运算符来移动资源,这样会避免拷贝,提高性能效率,右值完成移动后会马上销毁(析构)。
这样的右值存在的使命就是移动后走向死亡,所以叫做“将亡值”。
- 五种值类型辨析
辨析内容 |
左值 |
右值 |
自增自减(看符号) |
++i |
i-- |
解引用和取地址(解左取右) |
*p |
&a |
常见表达式 |
x = 1、cout << ’ ’ |
a+b、a&&b、a==b |
字面值(字串为左值) |
“ddd” |
1、3.14、‘r’ |
具名和不具名 |
具名的右值引用 |
不具名的右值引用 |
void foo(int&& t) t |
(5)值类别与型别的辨析
值类别 |
型别 |
左值、纯右值、将亡值 |
内建类型、基本类型(**指针、**引用...)、自定义类型 |
表达式的型别与他是左值和右值没有关系[一个是型别,一个是值类别],很多的移动构造函数内部对入参取地址完全没问题,尽管该参数的型别属于右值引用。
基于此,我们可以得知,任何形参都是左值。
当一个对象被用作右值的时候,用的是对象的值(内容);
当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
大概的意思就是说左值就有内存地址的,存活的生命周期较长的,
而右值一般是无法获取到内存地址的,生命周期是短暂的。还是以以上的代码为例子,
- 引用型别可分为四类
(1)左值引用:即绑定到左值的引用,必须绑定左值
int i = 1;
int &ref = i; //这里ref是绑定了i这个左值的引用
int &ref2 = 3; // 3 是右值,无法绑定
(2)右值引用
右值引用的作用是给开发者提供一个想要可以绑定临时变量的作用,可以通过右值引用符号“&&” 来实现
int &&i=1;//右值引用可直接引用右值,想要给 “ 1 ”一个固定地址,并给予一个变量名i
i = 3;//可以通过i再赋值
右值引用b延长了函数getX返回值的生命周期。延长临时对象生命周期并不是这里右值引用的最终目标,其真实目标应该是减少对象复制,提升运行性能(关闭RVO优化)。
右值---移动语义
移动构造
对于拷贝构造函数而言形参是一个左值引用,而不能是某些函数返回的临时对象,而且在拷贝构造函数中往往进行的是深复制,即一般不会破坏实参对象。而移动构造函数恰恰相反,它接受的是一个右值,其核心思想是通过转移实参对象的数据以达成构造目标对象的目的,也就是移动构造函数会修改实参对象,一般来说调用了移动构造函数之后,实参对象的相关变量资源就会被转移,原本实参的变量就会被置空,也就是实参就不能再使用了, 因此与其叫做移动构造函数不如叫做窃取构造函数更加的贴切。
那么在什么情况会发生移动构造的调用呢?比如在C++11的STL容器中,会根据具体情况自动调用移动构造函数,比如以下例子:
注意:和拷贝构造函数对于拷贝赋值运算符一样,移动构造函数也对于这一个移动赋值运算符,因为在移动语义中一般会置空实参的相关变量,所以需要注意在移动赋值运算符避免自己赋值给自己的情况,这样会给自己赋值的同时置空自己,在做无用功。
(3)Const常量引用,本质上也是左值引用的一种,但区别有二
一是无法通过这个引用改变引用地址的值【只读】,
二是它可以间接绑定右值(实际上是绑定了一种左值)【绑定右值】
int i = 1;
const int &ref = 1;
ref = 10; //错误,无法再赋值 ,因为ref是一个常量引用
const int &ref2 = 100;//可引用一个右值,本质上是100转换为一个变量,
// 再将ref2引用到这个变量上
总结:
左值引用只能引用左值,右值引用只能引用右值,
而const引用就可以同时引用左值和右值
(通过临时变量间接引用右值,实际上是左值引用)
(4)万能引用
令人眼花的是这个将亡值,它既可以代表一个左值,又可以代表一个右值,这是怎么的一回事呢?所谓的万能引用就是既可以引用左值,也可以引用右值的引用,
一般是在模板中的未知右值引用类型,需要根据规则进行推导型别的引用。
只要发生了类型推导就会是万能引用,在T&&和auto&&的初始化过程中都会发生类型的推导所以它们是万能引用。在这个推导过程中,初始化的源对象如果是一个左值,则目标对象会推导出左值引用;反之如果源对象是一个右值,则会推导出右值引用。
通过类型推导细分万能引用,关于万能引用主要涉及到引用折叠规则(类型推导)、完美转发两个方面。
完美转发
上面介绍了万能引用,它的一个重要用途就是进行完美转发,所谓完美转发指的是函数模板可以将自己的参数“完美”地转发给内部调用的其它函数,不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变。
在C++11使用标准库中的std::forward函数就可以试下完美转发:
参考博文:C++之右值引用_FlyerGo的博客-CSDN博客_c++右值引用
移动语义引发的思考之左值、右值相关推荐
- C++11新特性之左值右值及移动语句与完美转发
C左值右值 左值和右值的由来 什么是左值和右值 左值右值的本质 引用 左值引用 右值引用 移动语句与完美转发 移动语句 实现移动构造函数和转移赋值函数 stdmove 完美转发Perfect Forw ...
- c++ 左值 广义左值 右值 纯右值 将亡值
为什么C/C++等少数编程语言要区分左右值? 历史原因: C语言作为一门古老的编程语言,其设计初衷是为了在硬件资源有限的系统上进行高效的编程,因此其语法和语义设计相对较简单.左值和右值的概念最初是由C ...
- 表达式左值右值(C++学习)
左值右值是表达式的属性,该属性称为 value category.按该属性分类,每一个表达式属于下列之一: lvalue left value,传统意义上的左值 xvalue expiring val ...
- 左值/右值/左值引用/右值引用/move的用法介绍
目录 问题 左值和右值 概念总结: 需要用到左值的运算符: 引用分类 左值引用 右值引用 右值引用到底什么用? std::move()函数介绍 问题 什么是左值和右值? 什么是左/右值引用? 左/右值 ...
- std::move 左值右值 左值引用右值引用
参考:https://blog.csdn.net/daaikuaichuan/article/details/88371948 https://zhuanlan.zhihu.com/p/9458820 ...
- C++基础知识(二)--左值右值--逻辑表达式求值优化--逗号运算符与表示式--输入输出格式控制...
:一.C++左值右值概念 左值:c++将变量名代表的单元称为左值,而将变量的值称为右值,左值必须是内存中可以访问且可以合法修改的对象,因此只能是变量名,而不能是常量或表达式.即左值可以寻址. 右值:将 ...
- C、C++差异之左值右值
C与C++在语法细节上还是有一些差异的,虽然一般情况下可能这些差异不足以造成结果的区别,但有些代码确实会有影响. 这次,主要总结下左值右值的差异. 在C中,很多左值运算符的结果都不再是左值,然而在C+ ...
- 左值右值将亡值泛左值
左右值概念 简单理解 左值:赋值运算符左边的变量,可以接受右边值,例如 int a = 10; a就是一个左值右值:赋值运算符右边的值,这个值可以是一个变量也可以是一个常量,例如 int a = 10 ...
- C++易被忽略的知识点:移动语义 左值右值
目录 lvalue 和 rvalue rvalue 引用 移动语义 移动语义的概念 强制移动 lvalue 和 rvalue 每个表达式都会得到 lvalue 或 rvalue.它们的区别是,lval ...
最新文章
- html 颜色叠加图片,如何在背景图片上添加颜色叠加?
- 通过注册表开启“终端服务”
- selenium python_Python+Selenium基础入门及实践
- 团队-科学技术器-模块测试过程
- [PHP]对Json字符串解码返回NULL的一般解决方案
- java实现数据库内容修改_数据库更改到Java环境中实现可持续和平
- linux无filelength函数,Linux Shell 自定义函数(定义、返回值、变量作用域)介绍
- JDK源码解析之 java.lang.Integer
- Discuz素材资源下载官网门户+自带论坛 整站源码+带后台+带数据库
- 毕设日志——Fast RCNN
- python如何下载安装tensorflow_TensorFlow下载与安装
- Java集合详解2:LinkedList和Queue
- Atitit wsdl的原理attilax总结
- MySQL不同字段比较大小_mysql 字段定义 对 大小比较的影响
- python实现银行ATM系统
- HMTL中隐藏域(type=hidden)
- GPS是如何定位的?
- 弘辽科技:淘宝开店可以卖不同类型产品吗?会有问题吗?
- python批量下载bilibili视频_关于bilibili视频下载的一些小思路
- CVE-2017-7494紧急预警:Samba蠕虫级提权漏洞,攻击代码已在网上扩散