当前位置: 首页 > news >正文

C++ 左值与右值浅谈

左值与右值

  • 序言
  • 概念
    • 左值和右值的划分理解
    • 右值引用
      • 常量左值引用与右值引用
    • 移动语义
    • 引用折叠
    • 完美转发
  • 参考资料

序言

虽然平常都算是了解左值,右值的用法,但是好记性不如烂笔头,记下来供大家评鉴,有错改错,有善赞善,也是对于自己知识的一次梳理。

为什么要分清楚左值和右值?这是因为在理清楚左值和右值,合适为其设置适合的用法,能够有效减少资源开销。

但是,对于一些POD类型的资源,那就无所谓左值右值了,因为拷贝即移动,移动即拷贝。

接下来我以左值和右值的讨论依次简单讲解:左值和右值的概念,右值引用,移动语义,引用折叠,完美转发。明确左值和右值理清楚后,可以使用的主要用法。

:1. 开始看下面之前,需要注意的是!这些除了专业名词之外,基本都是基于个人理解去通俗诠释概括的,想直接看专业且全面的概念就找末尾的参考资料看。
2.以下有比较多的专业词汇,初学者建议慢慢看查询拓展。

概念

左值和右值的划分理解

左值(lvalue)和右值(rvalue)是C++11之前的概念,但是也通用到后面。
C++11及之后,划分为 泛左值(glvalue)、将亡值(xvalue,也称亡值,消亡值)和纯右值(prvalue)

左值(C++11之前):赋值运算“=”左边的变量
右值(C++11之前):赋值运算“=”右边的表达式左值(C++11及之后):非将亡值的泛左值,有地址的变量
右值(C++11及之后):纯右值或者将亡值,生命周期在表达式里。
int a = 15 + 29;
std::cout << &a;	// 0xeffc40
std::cout << &(15 + 29);	//error: Cannot take the address of an rvalue of type 'int'
std::cout << &"xzz";	//0xa16444

以简单的例子,这个a,承载类型的值,自身是有地址的,可以取地址值,这个就是左值。
15 + 29这个表达式的结果是纯右值,不能取地址值。

注:顺带一提,许多普通常量都是纯右值,但是字符串不是,是左值,因为普通常量都是可以用普通的机器码就可以表示其值,但是字符串无法合适表示,所以将其放置在常量区分配内存专门存放。

想必想了解左值和右值的人,估计都看过这个图:
在这里插入图片描述
或者是类似的,基本都是说将亡值泛左值右值的交集。

但是这其实是容易让人摸不着头脑的,但是本质角度上又是能说得过去的。

1. 将亡值被包含在右值这边,是因为其的    生命周期和右值是一样     的,都在一个表达式里面。
2. 将亡值被包含在泛左值这边,是因为其是      匿名对象,有地址,和左值是一样     的。

而上述也引申出了怎么判断将亡值。

将亡值:生命周期在一个表达式里,且是匿名对象有地址。

C++17的临时量实质化也是将亡值。

.

右值引用

右值引用T &&),顾名思义是引用右值的,无论是纯右值还是将亡值

右值引用是C++11引入的,值得注意的是右值引用的变量是个左值

因为其是完全符合左值定义的,众所周知,引用本质上是一种特殊的指针,可以这么认为,指针指向的值是右值,但是指针本身并不是右值。

所以你如果想右值引用 右值引用的变量 这样是不行的:

int &&a = 5;	// 编译正常,可以随意右值引用纯右值
int &&b = a;   	// 编译错误

右值引用的目的是延长将亡值的生命周期,减少资源开销,或者是为了移动语义服务,使其进行资源转移。

struct AA {};
AA createAA() {return AA();
}
int main() {AA &&a = createAA(); 
}

右值引用AA &&a接纳了本来表达式结束就要释放掉资源的匿名对象AA()

并可以任意更改匿名对象的资源。

常量左值引用与右值引用

在C++11之前,负责右值引用(T &&)功能的是常量左值引用(const T &),只不过和右值引用相比,常量左值引用无法修改其值,且只能用于拷贝语义不能用以移动语义
在这里插入图片描述
在这里插入图片描述
可以看出来,常量左值引用右值引用做的事情是一样的。

顺带一提,不建议用右值引用去引用POD类型的纯右值,因为纯右值要想被右值引用,就得先压栈地址,才能给其引用。

从开销上看,不如直接普通的赋值
在这里插入图片描述
在这里插入图片描述
就算单纯只看条数,右值引用用了3条,普通赋值才用了1条,开销一目了然。

.

移动语义

移动语义(Move Semantics)是 C++11 引入的一项重要特性,它使得实例对象的资源不通过拷贝的方式进行转移(除了POD类型)。

移动语义具体化其实就是移动构造函数。

struct Resource { ... }class XZZ 
{
public:...构造或者其他的实例化资源...移动构造函数XZZ(XZZ &&value) {this.m_resource = value.m_resource;value.m_resource = nullptr;}/// 移动赋值函数XZZ &operator =(XZZ &&value) {this.m_resource = value.m_resource;value.m_resource = nullptr;}...
private:Resource *m_resource = nullptr;
}

上面是个简单的例子,主要是为了理解移动是怎么来移动资源的。

如果m_resource不是指针,也可以通过使用std::move强行将value.m_resource转成右值来触发this.m_resource的移动构造,使得两个m_resource的资源进行移动达到同样的效果。

_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept {return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

std::move的效果便是强制将传进来的参数转成右值,一般可以将已经右值引用的变量或者将要释放的类型转成右值(将亡值),实现移动语义的功能。

再次提醒,如果资源是POD类型的,那用移动语义其实没有意义,因为移动就是拷贝,拷贝就是移动。

另外移动构造什么情况下可以编辑器会提供默认移动构造,什么情况下会弃置默认移动构造只能自己写的,这些内容不在本节重点,感兴趣可自行查看。
.

引用折叠

说回右值引用的类型,左值引用右值引用,或者右值引用左值引用,那到底是左值引用还是右值引用呢?

C++11中引入引用折叠规则(reference collapsing),通过模板或 typedef 中的类型操作可以构成引用的引用,此时适用引用折叠规则:右值引用的右值引用折叠成右值引用,所有其他组合均折叠成左值引用

废话不多说:
C++ 11新特性解析与应用
简单通俗来说,只有右值引用本身,和叠加两次的右值引用,类型才是右值引用类型,否则,含至少一个引用&的都是左值引用

不能直接声明一个超过两个&的类型

int a = 10;
int &&& b = a;	// error: 'b' declared as a reference to a reference

但是如果通过using或者typedef间接声明就可以了

typedef int& intR;
using intRe = int&;int a = 10;
intR && b = a;		// 等同于 int &b = a
intRe && c = a;		// 等同于 int &c = a

有了引用折叠,就可以好好使用类型擦除完美转发参数类型给别的函数或者类。
.

完美转发

所谓完美转发(prefect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。 ——《C++ 11新特性解析与应用》

完美转发关键点在于:

  1. 函数模板
  2. 函数模板参数类型是 类型&&
  3. 要接收函数模板参数的函数/类,实参用std::forward包装一下
template <typename _Ty, typename... _Type>
_Ty *createClass(_Type&&... args) {return new _Ty(std::forward(args)...);
}

这是个没什么实质意义的模板函数,仅是为了举例。

为什么要用std::forward

是因为右值引用args本身是左值,传进来本身如果是个右值的话,结果给到接收函数是个左值,那就不是“完美”转发了。

_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {return static_cast<_Ty&&>(_Arg);
}_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");return static_cast<_Ty&&>(_Arg);
}

所以需要转成右值的类型,而如果是左值的话,因为函数重载引用折叠的缘故,即使通过std::forward也是转成左值类型。

参考资料

《C++ 11新特性解析与应用》
《C/C++ 参考文档》

http://www.lryc.cn/news/437327.html

相关文章:

  • oracle 如何查死锁
  • 如何编写ChatGPT提示词
  • java项目之基于Spring Boot智能无人仓库管理源码(springboot+vue)
  • 大厂前端常见的笔试题目
  • 网络插件 Cilium 更换 Calico
  • SpringSecurity原理解析(二):认证流程
  • 数据中台 | 数据资源管理平台介绍
  • 智慧环保平台建设方案
  • SpringMVC映射请求;SpringMVC返回值类型;SpringMVC参数绑定;
  • 【第28章】Spring Cloud之Sentinel注解支持
  • 鼎捷新一代PLM 荣膺维科杯 “2023年度行业优秀产品奖”
  • 如何升级用 Helm 安装的极狐GitLab Runner?
  • 08 vue3之认识bem架构及less sass 和scoped
  • 静态库的制作
  • PHP在现代Web开发中的高效应用与最佳实践
  • 大数据-134 - ClickHouse 集群三节点 安装配置启动
  • 2024网络安全人才实战能力白皮书安全测试评估篇
  • [项目][WebServer][解析错误处理]详细讲解
  • 51单片机应用开发---数码管的控制应用
  • Vue3+Django5+REST Framework开发电脑管理系统
  • Java8函数式接口全攻略
  • 英文软件汉化中文软件教程asi exe dll 等汉化教程
  • HTTP 请求方式`application/x-www-form-urlencoded` 与 `application/json` 怎么用?有什么区别?
  • prometheus 集成 grafana 保姆级别安装部署
  • Apache SeaTunnel Committer 进阶指南
  • 组件上的v-model(数据传递),props验证,自定义事件,计算属性
  • mfc140u.dll文件错误的相关修复方法,4种方法修复mfc140u.dll
  • Redis中使用布隆过滤器解决缓存穿透问题
  • css百分比布局中height:100%不起作用
  • java程序员入行科目一之CRUD轻松入门教程(二)