C++20标准下的左值与右值

news2025/1/18 20:57:55

C++20标准下的左值与右值

  • 一、什么是左值与右值
  • 二、左值引用
  • 三、右值引用
  • 四、值类别
  • 五、标准库 move 函数
    • 5.1 用 static_cast将左值转换为右值
    • 5.2 使用 std::move 将左值转换为右值

一、什么是左值与右值

  • 左值:左值可以出现在赋值语句的左边或者右边
  • 右值:右值只能出现在赋值语句的右边,不能出现在赋值语句的左边

变量是左值,因此可以出现在赋值语句的左边。数字字面值是右值,因此不能被赋值。

有些操作符,比如赋值,要求其中的一个操作数必须是左值。结果,可以使用左值的上下文比右值更广。左值出现的上下文决定了左值是如何被使用的,例如:

int a = 0;
a = a + 1;

其中 a 变量被用作两种不同操作符的操作数。+ 操作符仅关心其操作数的值。变量的值是当前存储在和该变量相关联的内存中的值。加法操作符的作用是取得变量的值并加1.

变量a 也被用作 = 操作符的左操作数。= 操作符读取右操作数并写到左操作数。在这个表达式中,加法运算的结果被保存到与a相关联的存储单元中,而 a 之前的值则被覆盖。

在C++中所谓的左值一般是指一个指向特定内存的具有名称的值(具名对象),它有一个相对稳定的内存地址,并且有一段较长的生命周期。而右值则是不指向稳定内存地址的匿名值(不具名对象),它的生命周期很短,通常是暂时性的。基于这一特征,我们可以用取地址符&来判断左值和右值,能取到内存地址的值为左值,否则为右值。

二、左值引用

左值引用是编程过程中的常用特性之一,它的出现让C++编程在一定程度上脱离了危险的指针。当我们需要将一个对象作为参数传递给子函数的时候,往往会使用左值引用,因为这样可以免去创建临时对象的操作。非常量左值的引用对象很单纯,它们必须是一个左值。对于这一点,常量左值引用的特性显得更加有趣,它除了能引用左值,还能够引用右值,比如:

int& x = 8;         // 报错 initial value of reference to non - const must be an lvalue
const int& x = 9;   // 编译成功

返回左值引用的函数,连同赋值、下标、解引用和前置的 ++/-- 运算符,都是返回左值表达式的例子。我们可以将一个左值引用绑定到这类表达式的结果上。

返回非引用类型的函数,连同算数、关系、位运算符以及后置的 ++/-- 运算符,都生成右值。我们不能将一个左值引用绑定到这类表达式上,但我们可以将一个 const 的左值引用或者一个右值引用绑定到这类表达式上。

虽然常量左值引用可以引用右值的这个特性在赋值表达式中看不出什么实用价值,但是在函数形参列表中却有着巨大的作用。一个典型的例子就是复制构造函数和复制赋值运算符函数,通常情况下我们实现的这两个函数的形参都是一个常量左值引用,例如:

class X {
public:
    X() {}
    X(const X&) {}
    X& operator = (const X&) { return *this; }
};
X make_x()
{
    return X();
}
int main()
{
    X x1;
    X x2(x1);
    X x3(make_x());
    x3 = make_x();

    return 0;
}

如果把类X中const删除,则会编译失败,因为非常量左值引用无法绑定到make_x()产生的右值。常量左值引用可以绑定右值是一条非常棒的特性,但是它也存在一个很大的缺点——常量性。一旦使用了常量左值引用,就表示我们无法在函数内修改该对象的内容(强制类型转换除外)。所以需要另外一个特性来帮助我们完成这项工作,它就是右值引用。

三、右值引用

所谓右值引用(rvalue reference),就是必须绑定到右值的引用。我们通过 && 而不是 & 来获得右值引用。右值引用有一个重要的性质,就是它只能绑定到一个简要销毁的对象。因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。

类似任何引用,一个右值引用也不过是某个对象的另一个名字而已。对于左值引用,我们不能将其绑定到要求转换的表达式、字面常量或是返回右值的表达式。而右值引用有着完全相反的绑定特性:我们可以将一个右值引用绑定到这类表达式上,但不能将一个右值引用直接绑定到一个左值上。

int i = 43;
int &r = i;             // 正确,r引用i
int &&r = i;            // 错误,不能将右值引用绑定到一个左值上
int &r2 = i * 2;        // 错误,不能将左值引用绑定到一个右值表达式上
const int &r3 = i * 2;  // 正确,可以将const引用绑定到右值上
int &&r4 = i * 2;       // 正确,将右值引用r4绑定到右值表达式上

左值持久,而右值短暂。左值一般有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。

由于右值引用只能绑定到临时对象,我们得知:

  • 所引用的对象将要被销毁
  • 该对象没有其他用户

这两个特性意味着:使用右值引用的代码可以自由地接管所引用的对象的资源。

四、值类别

在进一步探讨右值引用之前,我们必须先了解一下这个概念:值类别。

值类别是C++11标准中新引入的概念,具体来说它是表达式的一种属性,该属性将表达式分为3个类别,它们分别是左值(lvalue)、纯右值(prvalue)和将亡值(xvalue)。

由于C++11中右值引用的出现,值类别被赋予了全新的含义。可惜的是,在C++11标准中并没能够清晰地定义它们,比如在C++11的标准文档中,左值的概念只有一句话:“指定一个函数或一个对象”,这样的描述显然是不清晰的。这种糟糕的情况一直延续到C++17标准的推出才得到解决。

oGHko0.png

  • 所谓泛左值是指一个通过评估能够确定对象、位域或函数的标识的表达式,简单来说,它确定了对象或者函数的标识(具名对象)
  • 而纯右值是指一个通过评估能够用于初始化对象和位域,或者能够计算运算符操作数的值的表达式。
  • 将亡值属于泛左值的一种,它表示资源可以被重用的对象和位域,通常这是因为它们接近其生命周期的末尾,另外也可能是经过右值引用转换产生的。

从本质上说产生将亡值的途径有两种,第一种是使用类型转换static_cast将泛左值转换为该类型的右值引用。

第二种是在C++17 中引入的,称它为临时量实质化,指的是纯右值转换到临时对象的过程。每当纯右值出现在一个需要泛左值的地方时,临时量实质化都会发生,也就是说都会创建一个临时对象并且使用纯右值对其进行初始化,这里的临时对象就是一个将亡值。

struct X
{
    int a;
};

int main()
{
    int b = X().a;
    return 0;
}

上述代码中,X()是一个纯右值,访问其成员变量a却需要一个泛左值,所以这里会发生一次临时量实质化,将X()转换为将亡值,最后再访问其成员变量a。

五、标准库 move 函数

5.1 用 static_cast将左值转换为右值

通常情况下,static_cast 只能用于其他合法的类型转换,但是,这里又有一条针对右值引用的特许规定:虽然不能隐式地将一个左值转换为右值引用,但我们可以用static_cast 显式地将一个左值转换为一个右值引用。

int i = 0;
int &&k = static_cast<int&&>(i);

虽然我们可以直接编写这种类型转换的代码,但是使用标准库 move 函数是容易得多的方式。而且,统一使用 std::move 使得我们在程序中查找潜在的截断左值的代码变得很容易。

5.2 使用 std::move 将左值转换为右值

虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型。我们还可以通过调用一个名为 move 的新标准库函数来获得绑定到左值上的右值引用,此函数定义在头文件 utility 中。

int a = 43;
int &&b = std::move(a);

std::move 告诉编译器:我们有一个左值,但我们希望像一个右值一样处理它。

我们必须认识到,调用 move 就意味着承诺: 除了对 a 赋值或销毁它之外,我们将不再使用它。在调用 move 之后,我们不能对移动后的源对象的值做任何假定。也就是说我们可以销毁一个移动后的源对象,也可以对它重新赋值,但不能使用一个移动后的源对象的值。

与大多数的标准库名字使用不同,对 move 我们不提供 using 声明,而是直接调用 std::move,而不是 move。这样做可以避免潜在的名字冲突。因为 move(以及forward 函数)的名字冲突要比其他标准库函数的冲突频繁的多,且多数情况下这种冲突并不是有意的。


参考文献:

  1. 《C++ Primer 第四版》
  2. 《C++ Primer 第五版》
  3. 《现代C++语言核心特性解析》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java设计模式-装饰者模式Decorator

介绍 装饰者模式的核心思想是通过创建一个装饰对象&#xff08;即装饰者&#xff09;&#xff0c;动态扩展目标对象的功能&#xff0c;并且不会改变目标对象的结构&#xff0c;提供了一种比继承更灵活的替代方案。需要注意的是&#xff0c;装饰对象要与目标对象实现相同的接口&…

VisualBox解决CentOS中yum安装失败的问题

怎么说呢&#xff0c;花了一个下午一个上午的时间&#xff0c;总算把这条命令运行成功了&#xff1a; yum install wget -y 打怪兽途中遇到了几个问题&#xff0c;总结一下&#xff1a; 1.ping 啥ip都是unkown&#xff0c;还有一种情况&#xff0c;就是ping之后就一直卡在那…

C++计算机课程设计 学生成绩管理系统 研究报告

课程设计内容 2.1 学生成绩管理系统 2.1.1 内容 主菜单模块 该模块主要用来实现整个系统的流程。主界面提供用户选择并调用各个子模块。 输入模块 当初次使用系统时&#xff0c;学生信息需要从键盘逐个输入。学生信息由学生的学号、姓名、性别、高等数学、英语、计算机和平均…

Qt基于CTK Plugin Framework搭建插件框架--CTK服务工厂

一、前言 注册服务的时候能够用服务工厂来注册&#xff1b; 访问服务getServeice中的plugin参数是执行ctkPluginContext::getService(const ctkServiceReference&)的插件&#xff0c;从而工厂根据执行的不同插件名称返回不同的服务实现 服务工厂的作用 在服务中可以知道…

达摩院2023十大科技趋势发布,生成式AI将进入应用爆发期

1月11日&#xff0c;达摩院2023十大科技趋势发布&#xff0c;生成式AI、Chiplet模块化设计封装、全新云计算体系架构等技术入选。达摩院认为&#xff0c;全球科技日趋显现出交叉融合发展的新态势&#xff0c;尤其在信息与通信技术&#xff08;ICT&#xff09;领域酝酿的新裂变&…

【关于Linux中----进程间通信方式之管道】

文章目录一、什么是管道二、匿名管道三、命名管道一、什么是管道 进程间通信主要目的为以下几点 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息…

STL forward_list 模拟实现

forward_list 概述 forward_list 是 C 11 新增的容器&#xff0c;它的实现为单链表。 forward_list 是支持从容器中的任何位置快速插入和移除元素的容器&#xff0c;不支持快速随机访问。forward_list 和 list 的主要区别在于&#xff0c;前者的迭代器属于单向的 Forward Ite…

二分法讲解

目录 一、前言 二、二分法理论 1、引导&#xff1a;猜数游戏 2、理论背景&#xff1a;非线性方程的求根问题 1&#xff09;非线性方程的近似解 2&#xff09;搜索法和二分法 3、用二分的两个条件 4、二分法复杂度 三、整数二分 1、在单调递增序列中找 x 或者 x 的后继…

使用python-pptx创建PPT演示文档功能实践

python对PPT演示文档读写&#xff0c;是通过第三方库python-pptx实现的&#xff0c;python-pptx是用于创建和更新 PowerPoint&#xff08;.pptx&#xff09;文件的 Python 库。 关于PPT演示文档与幻灯片模板的内容不是本文的重点&#xff0c;在此略过。 1. PPT基本结构在pyth…

Sophus降维、升维与欧拉角、旋转向量的爱恨情仇

0. 简介 在面对二维与三维之间的转换时&#xff0c;我们常常会困惑该如何去转换&#xff0c;在G2O中存在有理想的坐标转换工具&#xff0c;但是在Sophus中却缺乏这样的手段。之前在Sophus处简要的介绍了一下SE(2)与SE(3)的转换&#xff0c;最近发现之前的文章这部分需要拿出来…

Leetcode:654. 最大二叉树(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 直接模拟&#xff08;递归)&#xff1a; 原理思路&#xff1a; 索引版本&#xff1a; 问题描述&#xff1a; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&a…

Thymeleaf从入门到清晰使用

文章目录什么是thymeleaf&#xff1f;第一个Thymeleaf程序Thymeleaf详解配置常用标签最后什么是thymeleaf&#xff1f; 模板引擎&#xff1a; 前端交给我们的页面&#xff0c;是html页面&#xff0c;如果是以前开发&#xff0c;我们需要把他们转成jsp页面&#xff0c;jsp的好处…

ABB AC500 系列 PLC 与上位机iFix 的通讯配置

ABB PLC IP 及 MODBUS TCP/IP 协议设置 通过 IP config tool 配置设备 IP 在 软件中&#xff0c;有 3 种方式可以进入 IP config tool 的配置界面  双击 IP_settings&#xff0c;点击 IP config tool&#xff0c;即可进入 IP config tool 界面 点击菜单栏的 Tool→IP confi…

k8s 微服务spring boot JVM 监控

目录 1.前言 1.1 JVM监控方案 1.2 接入操作步骤 2. 服务开启actuator prometheus监控 2.1 示例参考添加依赖 2.2 配置prometheus监控 3 配置 prometheus 监控 4 配置jvm grafana dashboard 1.前言 服务 部署环境 监控 spring-demo k8s v1.22 Prometheus Operator …

进程相关概念

1、什么是程序&#xff0c;什么是进程&#xff0c;有什么区别 程序是静态的概念。例如&#xff1a;gcc xxx.c -o pro&#xff0c;磁盘中生成pro文件&#xff0c;叫做程序&#xff08;还未运行起来&#xff09; 进程是程序的一次运行活动&#xff0c;也就是程序跑起来了&#xf…

【Linux】信号机制(非实时信号)

目录 前言 一.信号的概念以及产生 1.什么是信号 2.信号分为两类 3.查看信号的命令 4.信号如何产生 1).通过软件产生 2).通过硬件产生 3).通过键盘组合键产生 二.信号的发送以及保存 1.信号如何发送 2.信号如何保存 1).概念 2).底层实现结构&&内核中的实现…

WampServer服务器设置控件

WampServer服务器设置控件 WampServer是一个web开发环境&#xff0c;是一个用于Windows操作系统的类似服务器的软件&#xff0c;由Romain Bourdon构建。该工具允许您开始构建web应用程序&#xff0c;并在Windows上使用Apache服务器2、PHP编程语言和MySQL数据库在本地网络上启动…

java 实现 springboot项目 使用socket推送消息,前端实时进行接收后端推送的消息

1 后端 1.1 添加依赖 在我们的springboot项目里面&#xff0c;添加依赖&#xff1b; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version>…

企业微信在职继承功能如何理解?怎么使用?

企业微信的在职继承功能就是企业管理员将在职员工的客户分配给其他在职员工跟进的一种方式&#xff0c;很方便&#xff0c;可以在工作出现变更时使用。 前言 企业微信之前的离职继承功能受到很多企业的青睐&#xff0c;企业能够将离职员工的客户分配给其他员工接手&#xff0c…

云天励飞在科创板获准注册:计划募资30亿元,陈宁为实际控制人

2023年1月10日&#xff0c;证监会发布公告&#xff0c;同意深圳云天励飞技术股份有限公司&#xff08;下称“云天励飞”&#xff09;首次公开发行股票注册。据贝多财经了解&#xff0c;云天励飞于2020年12月8日在科创板递交上市申请&#xff0c;2021年8月6日过会。 本次冲刺上市…