C++11_左值引用与右值引用

news2024/11/19 23:37:13

在C++11之前,是没有右值引用的概念的,在C++11之后才新增了右值引用。其实无论是左值引用还是右值引用都是给对象取别名。

认识左值和右值

什么是左值?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。

// 常见的左值
// 以下的p、b、c、*p、s都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
*p = 10;
string s("xxxxxx");

什么是右值?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边右值不能取地址,都是一些临时创建的对象

// 常见的右值 (临时对象)
10;                // 常量
x + y;             
string("xxxxx");   // 匿名对象

左值引用与右值引用

其实左值引用和右值引用非常好理解:左值引用就是给左值的引用,给左值取别名。右值引用就是对右值的引用,给右值取别名。

// 这里的b、p、*p、10、x+y都是上文代码中的
// 左值引用给左值取别名
int& r1 = b;
int*& r2 = p;
int& r3 = *p;
// 右值引用给右值取别名
int&& rr1 = 10;
int&& rr2 = x+y;

我们再来思考一下,左值引用能不能给右值取别名呢?或者说右值引用能不能给左值取别名呢?我们来看下面的代码 ps.    &代表左值引用、&&代表右值引用

int& rx1 = 10;                        // 报错
int& rx2 = x + y;                     // 报错
const int& rx3 = 10;                  // 正确
const string& rx4 = string("xxxxx");  // 正确

结论:左值引用不能直接给右值取别名,但是const左值引用可以(也间接说明了右值不可被修改)。

int&& rrx1 = b;            // 报错
int*&& rrx2 = p;           // 报错
int&& rrx3 = move(*p);     // 正确
string&& rrx4 = move(s);   // 正确

ps. 其实move()函数的本质就是强制类型转换,类似string&& rrx5 = (string&&)s; 也是可以编译通过的。想要深入了解move()函数的,请点击 move()文档

结论:右值引用不能直接给左值取别名,但是move(左值)之后就可以。

右值引用的作用(重点)

文章开头就已经说过,右值引用是C++11之后才引入的,左值引用是早已存在的。既然引用的意义是为了减少拷贝,那么左值引用已经存在了,为什么还要引入右值引用的概念呢?为了增加我们的学习难度?当然不是!其实左值引用只能解决 引用传参/引用传返回值 的场景,但有时候我们会遇到 传返回值 的场景,这个时候左值引用就管不了了,所以引入了右值引用来解决该问题。

左值引用的局限性

// 整形转字符串函数
string to_string(int value)
{
	bool flag = true;
	if (value < 0){
		flag = false;
		value = 0 - value;
	}
	string str;			// 临时对象
	while (value > 0)
	{
		int x = value % 10;
		value /= 10;
		str += ('0' + x);
	}
	if (flag == false)	str += '-';
	reverse(str.begin(), str.end());

	return str;			// 返回临时对象,出了作用域就会被销毁
}
int main()
{
    // 在string to_string(int value)函数中可以看到,这里
    // 只能使用传值返回,传值返回会导致至少1次拷贝构造
	string ret1 = to_string(1234);

	return 0;
}

所以,上面代码的执行过程会至少调用一次拷贝构造(VS2022编译环境下),下面我们来看看为什么会调用拷贝构造。

右值引用和移动语义

有了上面的理解,我们知道如果没有移动构造,就会调用拷贝构造,那么,什么是移动构造呢?移动构造本质就是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

大家再来思考一个问题:为什么右值的资源可以被窃取呢?被窃取资源后的右值的结果又会怎么样呢?在回答这个问题之前,先补充一个小知识,右值的分类

右值分为两种:

  • 纯右值(prvalue):内置类型右值。比如:10
  • 将亡值(xvalue):类类型的右值。比如:匿名对象、类型转换产生的临时对象

上例中函数to_string中的str属于左值,但中间产生的临时对象就是右值中的将亡值,将亡值的意思就是很快就要死亡的值。既然你马上就要消亡了,你的这些资源留着也是浪费,正好我也正需要这样的资源,不如把他们直接给我。就好比一个将亡的病人,在死亡之前签订了器官捐赠协议...(吧啦吧啦,你们自己脑补吧)看到这里相信上面的问题你们心里已经有了答案。所以说 “成为右值” 是一件非常危险的事,说不准你就已经被别人盯上了......

// 在上述代码中添加移动构造
string(string&& s)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{
    cout << "string(string&& s) -- 移动语义" << endl;
    swap(s);
}
// 再运行上面to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,
// 而是调用了移动构造,移动构造中没有新开空间和拷贝数据,所以效率提高了。

to_string的返回值产生的临时对象是一个右值,用这个右值构造ret2,如果既有拷贝构造,又有移动构造,调用就会匹配移动构造调用,因为编译器会选择最匹配的参数调用。那么这里就是一个移动语义

是每个类都必须有移动构造吗?不是的,只有深拷贝的类才有意义。举个例子:日期类可以有移动构造,但是没有意义。

不仅有移动构造,还有移动赋值

// 移动赋值
string& operator=(string&& s)
{
    cout << "string& operator=(string&& s) -- 移动语义" << endl;
    swap(s);
    return *this;
}
int main()
{
    string ret1;
    ret1 = to_string(1234);
    return 0;
}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义

这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。

另外,右值引用的作用不仅仅体现在传值返回上,像STL中的容器相关接口,如:push系列、insert系列,都提供了右值引用的版本,大大起到了提效的作用(减少了拷贝)

补充知识

// r1(右值引用本身)的属性是左值还是右值?
string&& r1 = string("xxxxxxx");

分析上述代码可知,string("xxxxxxx") 是一个右值,而r1引用了它,问r1本身是一个什么属性的值?这里直接说答案:左值。这里发生了一个退化,可能大家会觉得比较怪,我们看下图

完美转发 

模板中的&& 万能引用

我们先来看一段代码

template<typename T>
void PerfectForward(T&& t)
{}

问题:函数PerfectForward的形参类型是右值引用吗?

根据上文中的相关知识,我们会非常肯定的回答是的,是右值引用。但是在这里却不是,模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
    Fun(t);
}
int main()
{
    PerfectForward(10);            // 右值
    int a;
    PerfectForward(a);             // 左值
    PerfectForward(std::move(a));  // 右值
    const int b = 8;
    PerfectForward(b);             // const 左值
    PerfectForward(std::move(b));  // const 右值
    return 0;
}

如果上述代码中传入形参 t 的值是一个左值,那么Fun函数就会调用其左值版本,进而输出“左值引用”;如果传入形参 t 的值是一个右值,那么Fun函数中参数的属性还是左值(上文中有讲,右值退化成了左值),还是会调用其左值版本,进而输出“左值引用”。

所以,模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性,就需要用到完美转发。

// 将上述代码中的PerfectForward函数修改为
void PerfectForward(T&& t)
{
    Fun(std::forward<T>(t));
}

其中std::forward<T>(t)就是完美转发,std::forward 完美转发在传参的过程中保留对象原生类型属性。这样我们就可以得到我们想要的答案

std::forward VS std::move

相同点:

  • 本质上都是强制类型转换

不同点:

  • std::move一定会将一个左值转换为一个右值,使用std::move时不需要指定模板实参,模板实参是由函数调用推导出来的。
  • std::forward会根据左值和右值的实际情况进行转发,在使用的时候需要指定模板实参。

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

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

相关文章

YOLOv11改进策略【损失函数篇】| Shape-IoU:考虑边界框形状和尺度的更精确度量

一、本文介绍 本文记录的是改进YOLOv11的损失函数&#xff0c;将其替换成Shape-IoU。现有边界框回归方法通常考虑真实GT&#xff08;Ground Truth&#xff09;框与预测框之间的几何关系&#xff0c;通过边界框的相对位置和形状计算损失&#xff0c;但忽略了边界框本身的形状和…

PV大题--专题突破

写在前面&#xff1a; PV大题考查使用伪代码控制进程之间的同步互斥关系&#xff0c;它需要我们一定的代码分析能力&#xff0c;算法设计能力&#xff0c;有时候会给你一段伪代码让你补全使用信号量控制的操作&#xff0c;请一定不要相信某些人告诉你只要背一个什么模板&#…

Java线程入门

目录 一.线程相关概念 1.程序&#xff08;program&#xff09; 2.进程 3.线程 4.其他相关概念 二.线程的创建 1.继承Thread 2.Runnable接口 3.多线程机制&#xff08;重要&#xff09; 4.start() 三.线程终止--通知 四.线程&#xff08;Thread&#xff09;方法 1.常…

fastAPI教程:数据库操作

FastAPI 六、数据库操作 FastAPI支持操作各种数据库&#xff0c;但本身并没有内置关于任何数据库相关的模块。因此我们可以根据需求使用任何数据库&#xff0c;包括关系型&#xff08;SQL&#xff09;数据库&#xff0c;例如&#xff1a;PostgreSQL、MySQL、SQLite、Oracle、…

【AGC005D】~K Perm Counting(计数抽象成图)

容斥原理。 求出f(m) &#xff0c;f(m)指代至少有m个位置不合法的方案数。 怎么求&#xff1f; 注意到位置为id&#xff0c;权值为v ,不合法的情况&#xff0c;当且仅当 v idk或 v id-k 因此&#xff0c;我们把每一个位置和权值抽象成点 &#xff0c;不合法的情况之间连一…

【JVM】基础篇

1 初识JVM 1.1 什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 Java源代码执行流程如下&#xff1a; 分为三个步骤&#xff1a; 1、编写Java源代码文件。 …

自动驾驶系列—深度剖析自动驾驶芯片SoC架构:选型指南与应用实战

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

认知杂谈74《远离渣女陷阱,拥抱健康情感》

内容摘要&#xff1a; 渣女在感情中使用甜言蜜语陷阱&#xff0c;利用男性渴望理解和关爱的心理&#xff0c;通过虚假承诺和情感操控来获得利益。 男性易陷入这种陷阱&#xff0c;因为他们可能因压力大、感性而易受感动。为了避免这种情况&#xff0c;男性需要辨别言行一致性&a…

【含文档】基于Springboot+Vue的国风彩妆网站(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

软件设计之SSM(4)

软件设计之SSM(4) 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 尚硅谷新版SSM框架全套视频教程&#xff0c;Spring6SpringBoot3最新SSM企业级开发 资料可以去尚硅谷官网免费领取 学习内容&#xff1a; 基于配置类方式管理Bean 完全注解开发第三…

共模电感工作原理:【图文讲解】

共模电感&#xff0c;相信做电源较多的朋友用的比较多&#xff0c;而做消费级产品的朋友或许用的不是那么的多。但是还是有必要了解了解。 先上图&#xff0c;看看它长什么样子&#xff1a; &#xff08;实物图&#xff09; &#xff08;结构图&#xff09; 很显然&#xff0…

【Ubuntu】安装常用软件包-mysql

我的几个服务是部署在docker的同一个网络里&#xff0c;这样相互访问就可以通过docker容器的名字访问&#xff0c;比如容器A访问容器B&#xff0c;就可以http://B:8080/xxx 这样访问&#xff0c;不用关心ip是多少。 所以mysql前面文章给安装到主机里&#xff0c;感觉有点坑自己…

02.usePrevious

在 React 开发中&#xff0c;有时我们需要访问组件的前一个状态或属性。这在进行比较、动画或其他需要历史数据的操作时特别有用。usePrevious 钩子提供了一种简单而有效的方式来存储和访问前一个值。以下是如何实现和使用这个自定义钩子&#xff1a; const usePrevious valu…

【数据类型】C和C++的区别

文章目录 一、字符串二、布尔类型 bool三、数据的输入和输出 C和C在数据类型上打区别不大&#xff0c;下面就二者在这方面的部分区别做比较。 一、字符串 C语言和C在字符串的定义和书写风格上略有差异。 C风格字符串&#xff1a; char str[]"hello";C风格字符串 st…

社交内容电商中的新机遇:2+1链动模式AI智能名片商城小程序

在当今的电商世界里&#xff0c;社交内容电商正蓬勃发展。这种模式基于高质量内容&#xff0c;将有着共同兴趣爱好的用户聚集起来形成社群&#xff0c;随后引导用户进行裂变式的传播与交易。无论是像微信、微博、快手、抖音、今日头条这样的平台形式&#xff0c;还是网红、“大…

算法笔记(四)——模拟

文章目录 替换所有的问号提莫攻击Z字形变换外观数列数青蛙 模拟算法就是根据题目的要求&#xff0c;题目让干神马就做神马&#xff0c;一步一步来 替换所有的问号 题目&#xff1a;替换所有的问号 思路 从左到右遍历整个字符串&#xff0c;找到问号之后&#xff0c;就⽤ a ~ z…

QT系统学习篇(2)- Qt跨平台GUI原理机制

一、Qt工程管理 新建项目&#xff1a; 我们程序员新建项目对话框所有5类项目模板 Application: Qt的应用程序&#xff0c;包含Qt Quick和普通窗口程序。 Library: 它可以创建动态库、静态库、Qt Creator自身插件、Qt Quick扩展插件。 其他项目: 创建单元测试项目、子目录项目…

自动驾驶系列—自动驾驶MCU架构全方位解析:从单核到多核的选型指南与应用实例

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

五子棋双人对战项目(3)——匹配模块

一、分析需求 二、约定前后端接口 三、实现游戏大厅页面&#xff08;前端代码&#xff09; 四、实现后端代码 五、线程安全问题 六、忙等问题 一、分析需求 需求&#xff1a;多个玩家&#xff0c;在游戏大厅进行匹配&#xff0c;系统会把实力相近的玩家匹配到一起。 要想实…