C++语法——右值引用、移动构造和赋值、万能引用和转发、move和forward底层实现

news2024/12/23 13:58:48

目录

一.右值引用

(一).何为右值

(二).右值引用

(三).右值和左值的互相传递

①左值->右值引用

②右值->左值引用

(四).右值引用的自身属性

二.移动构造和移动赋值

 (一).移动构造

 (二).移动赋值

三.转发

(一).万能引用

(二).完美转发

四.move和forward底层实现方式

(一).move底层实现

(二).forward底层实现


一.右值引用

(一).何为右值

不能取地址的就是右值。例如:字面常量、临时变量。

//1就是右值
int i = 1;
//max返回值是临时变量,也就是右值
int n = max(1, 2);

(二).右值引用

左值引用是对左值的引用,顾名思义,右值引用就是对右值的引用。

右值引用符号为&&,使用方式与左值引用相同,但符号和引用对象属性不同。

左值引用右值引用
符号&&&
引用对象左值(可取地址的变量)右值(不可取地址)
使用方式

int j = 1;

int& a = j;

int&& a = 1;

(三).右值和左值的互相传递

左值和右值无法直接传递。

int main() 
{
	int a = 1;
	int& b = a;//正确,左值引用

	int&& c = b;//错误。右值引用不能传左值

	int& d = 1;//错误。左值引用不能传右值
	return 0;
}

①左值->右值引用

该函数参数为左值,返回值为右值。

作用是将左值参数强制转化成右值引用。 

int a = 1;
int&& c = move(a);//将a强转成右值

②右值->左值引用

左值引用加上const修饰符即可。

const int& d = 1;

(四).右值引用的自身属性

右值引用本身是左值属性。

因此,如果给左值引用传递右值引用是可以的。

int&& a = 1;//a为右值引用,但自身属性是左值
int& b = a;//正确,给左值引用传递左值

这该怎么理解呢?

《C++ Primer》对此给出了相关解释,大意如下:

左值是“持久”的,右值是“短暂”的。即左值只要不出作用域就可以一直存在,但右值只能在使用时的瞬间“存活”(参考函数返回值)。

因此,当进行右值引用后,引用本身可以一直在作用域中存在,那么它就是左值。

当然,可以使用另一种方式证明:取地址。

左值可以取地址,右值不可以取地址。

int main() 
{
	int a = 1;//a可以取地址,是左值
	int&& b = 3;
	cout << "a地址: " << &a << endl;
	cout << "b地址: " << &b << endl;
	return 0;
}

 不妨总结一下:

左值右值左值引用右值引用
举例int a = 1;string str = "abc";int& b = a;int&& c = 1;
属性左值右值左值左值
取地址不能
转化

接收右值:

直接传

接收左值:

接收右值:

+const

接收左值:

move函数 

二.移动构造和移动赋值

C++11引入右值引用后,很大的作用便是移动构造与赋值。

比如官方STL库中就提供了相关函数:

 (一).移动构造

移动构造的目的在于减少因为参数是左值时引发的重复拷贝的问题。 

以string为例进行说明:

截取如下代码:

class String {

	...

	explicit String(const char* a = "")//默认构造
	{
		_size = strlen(a);
		_capacity = _size;
		_a = new char[_capacity + 1];
		strcpy(_a, a);
		cout << "构造函数\n";
	}

	String(const String& st)//拷贝构造
		:_a(nullptr)
	{
		String tmp(st.c_str());//调用构造函数
		swap(tmp);
		cout << "拷贝构造\n";
	}

    ...

	
};
String To_string(int value)//将int转为string
{
    ...
    String str;
    ...
    return str;
}
int main()
{
	String str = To_string(20);
	return 0;
}

当我们执行这个程序时,会调用2个默认构造和1个拷贝构造:

分别是to_string内部生成str时调用默认构造、返回临时变量时调用string拷贝构造,但是string拷贝构造内部又会先调用默认构造。

其实这还是优化后,如果没有编译器优化,main函数中str也会再调一次string拷贝构造。

 而这一切的“罪魁祸首”是什么呢?——to_string的返回值。

是的,因为to_string内部会生成一个string对象,而该对象是局部变量,出了函数作用域就销毁,因此只能调用拷贝构造to_string内部的对象。

这还只是string类型拷贝构造,如果是更加复杂的类型,拷贝构造往往会造成更多资源的占用。

正因如此,移动构造派上了用场:

String(String&& st)//移动构造函数,但是参数为右值
	:_a(nullptr)
{
	swap(st);
	cout << "string移动构造\n";
}

移动构造的参数为右值,所以当to_string返回str时,会被移动构造接收。

虽然str本身为左值属性,但是因为此时str是“将亡值”,即出了函数作用域就会被销毁,编译器会将这种“即将死亡”的值识别为右值。

在移动构造内部,会将右值的数据与自身数据进行交换。因为右值作为“暂时存在的数据”,把数据交给目标对象,目标对象把“舍弃”的数据交给右值,正好可以“延续”目标数据且消除原本数据。

这时,接收to_string返回值时只需要一个一个移动构造即可: 

 (二).移动赋值

移动赋值的目的与移动构造类似,在于减少因为赋值造成重复拷贝的问题

以string为例,其中赋值重载通过调用了拷贝构造函数实现。

class String {

	...

	String& operator=(const String& st)//赋值重载1
	{
		String tmp(st);//调用拷贝构造
		swap(tmp);
		cout << "string赋值\n";
		return *this;
	}
	String& operator=(const char* str)//赋值重载2
	{
		String tmp(str);
		swap(tmp);
		cout << "char*赋值\n";
		return *this;
	}

    ...

	
};
int main()
{
	String str;
	cout << "--------------------------------\n";
	str = To_string(1);
	return 0;
}

 当执行这个程序时,会有多个构造、拷贝构造被调用:

 而这其中,属于因为赋值重载而调用的就有三个。

 因为赋值重载的参数是左值引用,不能像右值引用那样交换数据,只能调用拷贝构造获取数据。

由此,移动赋值应运而生:

与移动构造相同,移动赋值也是直接与右值交换数据。 

String& operator=(String&& st)
{
	swap(st);
	cout << "string移动赋值\n";
	return *this;
}

 此时,只需要将to_string的返回值作为右值传给移动赋值即可。

三.转发

(一).万能引用

首先,万能引用只存在与模板编程中

万能引用就是引用形参既可接收左值也可接收右值,其符号与右值引用相同,但必须是模板。

即当模板的参数是右值引用的形式,如果实参是左值就是左值引用,右值就是右值引用。

 例如下列代码:

void Print(int& a)
{
	cout << "左值" << endl;
}

void Print(int&& a)
{
	cout << "右值" << endl;
}

template<class T>
void func(T&& t)//万能引用
{
	Print(t);
}

int main()
{
	int a = 0;
	func(a);//传左值
	func(1);//传右值
	return 0;
}

(二).完美转发

上述代码有一个问题,尽管func(1)传入的是右值,但是因为右值引用本身是左值,当调用Print函数时,会调用左值版本,这不符合我们的预期,因为明明传入的是右值:

 这时,就需要使用完美转发forward,它会保持传入实参的属性不变:

void func(T&& t)
{
	Print(std::forward<T>(t));
}

四.move和forward底层实现方式

(一).move底层实现

首先看一下move函数底层代码:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_case<typename remove_reference<T>::type&&>(t);
}

其中参数T&&是万能引用,可接收左值或右值。

返回值很特殊,typename remove_reference<T>::type的含义就是去掉T的引用类型

remove_reference本身是模板类,它的作用就是返回一个类型,所以这个类里面只有成员类型

通过remove_reference源码可以看到,不管传入的是左值引用还是右值引用,它都只会返回这个值去掉引用后的类型。

我们以int为例,不管传入int&还是int&&,经过remove_reference<T>后,返回的都是int。

template <typename T>
struct remove_reference{
    typedef T type;  //成员类型
};

template <typename T>
struct remove_reference<T&> //左值引用
{
    typedef T type;//返回T本身的类型
}

template <typename T>
struct remove_reference<T&&> //右值引用
{
   typedef T type;//返回T本身的类型
}

static_case作用是强制类型转换,可以将左值强转成右值,move中是强转成右值引用。

因此,move底层代码可以翻译成如下形式:

template <typename T>
int&& move(T&& t)
{
    return (int&&)(t);
}

于是,我们清楚的发现:move函数就是通过remove_reference获取引用对象本身的类型,强转成右值引用的方式实现的

(二).forward底层实现

这是forward底层代码:

template <typename T>
T&& forward(typename std::remove_reference<T>::type& param)//左值引用
{
    return static_cast<T&&>(param);//万能引用
}

template <typename T>
T&& forward(typename std::remove_reference<T>::type&& param)//右值引用
{
    return static_cast<T&&>(param);//万能引用
}

 有了move的基础,forward就不难理解了。

它通过remove_reference来区分传入的参数是左值引用还是右值引用,然后调用具体的重载forward函数。

再通过万能引用的形式,根据param的具体类型返回左值引用还是右值引用。

源码可以翻译成如下形式(int为例):

template <typename T>
T&& forward(int& param)//左值引用
{
    return (T&&)(param);//万能引用
}

template <typename T>
T&& forward(int&& param)//右值引用
{
    return (T&&)(param);//万能引用
}

参考文章:

聊聊C++中的完美转发 - 知乎 (zhihu.com)

C++高阶知识:深入分析移动构造函数及其原理 | 音视跳动科技 (avdancedu.com)

参考书籍:

《C++ Primer》 

程序是我的生命,但我相信爱她甚过爱我的生命。——未名


如有错误,敬请斧正 

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

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

相关文章

艾美捷Bio-Helix CCH321 超敏ECL化学发光试剂盒(皮克级)特点

UltraScene Pico Plus Western底物是一种基于鲁米诺的增强化学发光底物&#xff0c;它敏感且与辣根过氧化物酶&#xff08;HRP&#xff09;偶联的二级抗体进行免疫印迹兼容。UltraScene Pico Plus Western Substrate具有卓越的灵敏度和长信号持续时间&#xff0c;可实现抗原的低…

Day09--导入小程序项目,初步安装和使用vant组件库

1.拿到老师的资料mp_5 ************************************************************************************************************** 2.Day09--的大概流程 *****************************************************************************************************…

MySQL数据库期末考试试题及参考答案(04)

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 一、填空题 MySQL中提供了____关键字&#xff0c;可以在查询时去除重复的值。使用ORDER BY对查询结果进行排序时&#xff0c;默认是按____排列。SELECT语句中&#xff0c;用…

Android启动优化之多线程依赖线程池

背景 开发中会存在多个任务之间互相依赖&#xff0c;运行非常慢的情况&#xff0c;譬如Android在主线程中初始化多个SDK导致App启动慢的情况&#xff0c;搜索一下发现业界的通用做法是构造任务的有向无环图&#xff0c;拓扑排序生成有序的任务列表&#xff0c;然后用线程池执行…

[JavaScript] 用电脑计算圆周率评估计算性能

据说全球第一台计算机是在1946年面世的&#xff0c;那它的计算性能是怎样的&#xff0c;至今2022年&#xff0c;发展这么多年&#xff0c;现在的普通计算机性能又是怎样的呢&#xff0c;接下来做一个实验&#xff0c;评估计算性能 文章目录1. 设计2. 编程3. 测试1. 设计 先写一…

[第十三篇]——Docker Compose

Docker Compose Compose 简介 Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose&#xff0c;您可以使用 YML 文件来配置应用程序需要的所有服务。然后&#xff0c;使用一个命令&#xff0c;就可以从 YML 文件配置中创建并启动所有服务。 如果你还不了解…

G1D18-WarshallFloyd课程报告matlab下载

今天先从算法开始吧嘿嘿~ 一、DP &#xff08;一&#xff09;Warshall求闭包 1、DP大概看明白啦~ 2、一会再看一下基于邻接表的暴搜 &#xff08;二&#xff09;Floyd完全最短路径的Floyd算法 欸嘿~~基本上好啦还差一点图的遍历晚上问问同学吧&#xff01; 啊哈大概看了一…

BUUCTF·[WUSTCTF2020]大数计算·WP

BUUCTF在线评测 (buuoj.cn) 附件 flag等于 wctf2020{Part1-Part2-Part3-Part4} 每一Part都为数的十六进制形式&#xff08;不需要0x)&#xff0c;并用 - 连接 Part1 2020*2019*2018* ... *3*2*1 的前8位 Part2 520^1314 2333^666 的前8位 Part3 宇宙终极问题的答案 x,y,z绝…

CF461B Appleman and Tree题解

洛谷题面 感觉是非常经典的一道题&#xff0c;最近好像总是见到&#xff0c;今天也算给它做了&#xff0c;发一篇题解来记录一下。 这道题是一道树形 DP 题&#xff0c;设 f[u][0/1]f[u][0/1]f[u][0/1] 表示 uuu 点属于一个无黑点 /// 有且仅有一个黑点的联通块时的方案数。我…

【HDR】Deep high dynamic range imaging of dynamic scenes

文章目录一、贡献二、数据集构建三、算法框架3.1 对齐模块3.2 合成模块3.3 损失函数四、实验一、贡献 Paper&#xff1a; Deep high dynamic range imaging of dynamic scenes Code&#xff1a;https://github.com/TH3CHARLie/deep-high-dynamic-range 首次提出使用机器学习方…

Pdfjs使用

pdfjs使用一、下载二、Springboot引入pdfjs三、利用PDFJS预览pdf文件并加水印四、后端将pdf添加水印参看链接一、下载 pdfjs官方地址 二、Springboot引入pdfjs 针对于pdfjs方面有用的只是pdf这个包下面和viewer.html这个html页面viewer.html是我们用来展示pdf的页面不需要改但…

高压功率放大器在超声悬浮中的应用研究

高压功率放大器的叫法对于不同的人来说是完全不同的&#xff0c;有人叫功率放大器&#xff0c;也有人叫电压放大器&#xff0c;但它们都是指同一个电子测量仪器设备&#xff0c;主要是指内部能够拥有电压和功率放大电路&#xff0c;可以把微弱的外部信号进行放大输出的放大器。…

在华为云 OSC 上快速部署 EMQX MQTT 集群

EMQX Kubernetes Operator 是 EMQ 发布的一个封装、部署和管理工具&#xff0c;也是一个特定的应用控制器&#xff0c;方便 DevOps 人员在 Kubernetes 上编排 EMQX MQTT 消息服务集群&#xff0c;管理其生命周期。 华为云原生基础设施&#xff08;云容器引擎 CCE、容器镜像服务…

索引数据结构千千万 , 为什么B+Tree独领风骚

索引的由来 大数据时代谁掌握了数据就是掌握了流量&#xff0c;就是掌握的号召力。面对浩瀚的数据如何存储并非难事&#xff0c; 难点在于如何在大数据面前查询依旧快如闪电&#xff01; 这时候索引就产生了&#xff0c;索引的产生主要还是借鉴于图书管理员书签的功能。在大数…

谷歌、微软、Meta?谁才是 Python 最大的金主?

你知道维护 Python 这个大规模的开源项目&#xff0c;每年需要多少资金吗&#xff1f; 答案是&#xff1a;约 200 万美元&#xff01; PSF&#xff08;Python 软件基金会&#xff09;在 2022 年 6 月发布了 2021 的年度报告&#xff0c;其中披露了以下这份支出明细&#xff08;…

大家介绍一篇学生选课系统的设计与实现

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下&#xff0c;你想解决的问…

若依框架字典配置以及使用(结合vue和emelent)

一&#xff1a;字典数据创建&#xff08;我们公司是后端做的&#xff0c;前端不用管&#xff09; 1.首先新建要指定的默认角色 &#xff08;1&#xff09;必须用管理员账号登录才能看到角色管理 &#xff08;2&#xff09;具体怎写&#xff0c;可以参考已有的数据&#xff08…

低代码平台中的“模型驱动”与“表单驱动”有何区别?

低代码定义&#xff1a; 低代码是近几年比较火的一种应用程序快速开发方式&#xff0c;它能帮助用户在开发软件的过程中大幅减少手工编码量&#xff0c;并通过可视化组件加速应用程序的高效交付。&#xff08;低代码的定义来自Forrester报告&#xff0c;被认为是低代码一词的起…

坐标的变换

在QPainter可以使用以下函数变换坐标&#xff1a; QPainter&#xff1a;&#xff1a;scale()缩放坐标系统QPainter&#xff1a;&#xff1a;rotate()顺时针旋转QPainter&#xff1a;&#xff1a;translate()平移QPainter&#xff1a;&#xff1a;shear()围绕原点来扭曲坐标系统…

[附源码]java毕业设计小超市进销存管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…