C++相关概念和易错语法(28)(可变模板参数、编译时和运行时逻辑、emplace_back)

1.可变模板参数

在C语言中我们学习的第一个函数就是printf,这个函数有一个特点,即支持任意个参数,即可变参数。C++中引入了可变模板参数,我们可以在C++中利用模板函数实现像printf那样的功能。但众所周知C语言是没有模板函数的,所以C和C++在任意函数参数个数的实现上是有明显区别的,后面会提到。

要理解可变模板参数,我们首先要会分析一些符号,在其它语法中符号的含义都显而易见,但在可变模板参数这里却是个大难点,理解清楚这个,再结合一些递归思维,可变模板参数就很简单了

我们先把代码展示出来


#include <iostream>
using namespace std;template<class T>
void Fun(T&& cur)
{cout << "end" << endl;
}template<class T, class...Args>
void Fun(T&& cur, Args&&...args)
{cout << typeid(T).name() << endl;Fun(args...);
}int main()
{Fun(1, 2, 3);return 0;
}

结果是

第一时间看上去确实很懵,我会按顺序逐句讲解用法

(1)模板参数的含义

(2)对函数参数和调用函数的理解

(3)对终止条件的设定

理解清楚上述代码后,我们便可理解其中的递归思想,当我传任意个参数时,第一个参数会被单独抓出来实例化T,其余的参数放进参数包。当利用完T之后就丢了,再次调用这个函数时传递参数包,相比较上一次调用这个函数就少了一个参数(T被单独拿出来了),如此往复,就像给一个人装满东西的袋子,那个人只需要将袋子里面的东西一样一样拿出来,就能处理所有袋子里的东西。

但是思维严谨一点的就会想到,我们需要设定一个终止条件,当递归到最后调用Fun(1)时,1可以成功匹配,而参数包class...Args可以匹配0个参数,当再调用Fun()时就没有参数了,class T不是参数包,必须匹配一个参数,这个时候就会报错,所以我们要单独写一个Fun()来终止调用。

我们也可以像之前展示的写法那样,在还剩最后一个参数的时候就进行终止,也就是我们写一个单参数的模板函数在可变模板参数函数前面,当调用Fun(1)的时候直接调用那个终止的模板函数,就不多走一趟可变模板参数函数。

我们可以很明显地发现,前后两次int的数量不一样,后者是因为提前终止,在还剩一个参数的时候就终止递归调用了。

(4)终止函数的位置

我前面说过,终止函数要写在可变模板参数函数的前面,至少要在它前面声明,这是因为C/C++编译器在找函数的时候是从调用位置起向前找而不会向后找,在C语言中有的编译器会优化导致可以向后找普通函数,但在C++这里就没这么幸运了,因为编译模板本身就需要很大的性能开销,所以必须严格遵守向上查找的规则,所以任何函数一律不像后找。

编译器从调用位置向前找,找到最匹配的那一个,其中显然Args参数包参数个数为0的优先级就要低一些,优先匹配的是有一个模板参数的函数。

(5)...的其它应用方式

还有一些...的用法比较常见,下面会分析一下,顺便再次加深对这个符号的理解

sizeof...(arg)的特殊处理自然对应sizeof在此的特殊功能,我们稍微记一下就好。

我们只需要记住,当参数包args出现时一定要使用...,在主要重复部分的后面加上...,其余不需要深究,args可以作为整个函数参数包的名字,也可以作为每个函数参数的名字,很灵活。

2.编译时和运行时逻辑

要理解C和C++在实现可变参数上操作的不同,我们要引入编译时和运行时逻辑。

C语言中可变参数是利用类似数组实现可变参数(了解),在代码跑起来后依次解析每个参数,这个过程也可叫运行时逻辑。每个参数的类型,干什么都是在运行到该代码语句时确定的。这样有个缺点就是不安全,因为编译时、链接时都检查不出来错误,运行时可能程序就出问题了。

在C++中可变参数的实现依赖模板函数,编译时递归推导解析参数,但这是编译时逻辑。区别在于运行时允许可变参数当一个特殊的角色,编译时不报错,但在运行的时候才确定参数的类型、功能等。但编译时要求必须在编译的时候就完全展开,也就是说当程序运行起来的时候,模板的概念就完全消失了,也没有所谓的特殊代码,执行的都是已展开的普通的代码。

那么这两者的区别映射到我们的代码书写上又有什么不同呢?

编译的时候,编译器会对所有的可变模板参数展开处理,比如函数声明的是void Fun(Args...args),那么所有含有Args和args的语句都要进行处理,如sizeof...(args)会被直接转为int嵌入到代码中,Fun(args...)会被直接转为Fun(1, 2, 3, 4)这种普通形式。

但是这似乎并没有完全展现出编译时和运行时的区别。那我们看一下下面这段代码:

这段代码编译时报错了。但我们按照运行时逻辑来看这段代码是完全没有问题的。当最后调用Fun函数,即Fun(8)时,8被T占领,参数包的个数为0,此时应该走else语句,终止。

但是这里就出现了个最大的错误,if、for这种语句是典型的运行时逻辑,在程序运行起来了且当运行到该位置的时候才知道if条件是不是为真,for应该循环几次。在上面的代码中,我们依靠的是else来终止函数确实看上去没什么问题,但是我们要带入编译器的视角,当编译的时候编译器只管将sizeof...(args)换成数字,检查语法上有没有问题,至于if-else要实现什么东西编译器是完全管不着的所以在编译器的视角上,我们并没有写终止函数,编译器找不到,最后一次匹配不了函数,于是就报错了。假设运行起来了,我们确实发现代码是能跑通的,因为不存在匹配不上的情况,但是编译器都报错了,谈论运行的逻辑毫无意义。

看看下面这段代码,再对这两种逻辑加深印象

其实我们仔细带入运行时逻辑就会发现,上面的那个void Fun()根本就没有任何用处,因为if(sizeof...(args))就决定了当无参时是会直接走else语句,直接返回的。但这只是运行时逻辑,按编译时逻辑来讲Fun()是必须存在的。编译器只管展开可变模板参数,Fun(args...)虽然在if内,但它不会进行任何判断,它只管不停地递归,找函数,当它递归到最后的Fun()时,匹配上我们所写的函数就停下来,找不到就报错。

3.emplace_back

emplace_back兼容部分push_back的用法,如emplace_back(1)和push_back(1)

但是emplace_back(底层是用可变模板参数实现的)还支持另外一种写法,当容器是一个pair<string, string>时,可以使用emplace_back("苹果", "apple")这种写法。"苹果", "apple"这两个参数会被emplace_back的参数包args接收,在emplace_back内部new出一个新的空间时,会直接使用类似new pair<string, string>(args...)将整个args传到构造函数那边去。在构造函数那边同样有个可变模板参数实现的构造函数,它将除pair<string, string>以外的成员变量都初始化后,再将参数包args传过去,构造出pair(vector<string, string>可没有专门为<string, string>设置的构造,而pair有,所以要单独设置一层可变模板参数实现的构造函数)

emplace_back只需要走一层构造就行了,push_back则是构造+拷贝构造/移动拷贝,效率要高一些。但是emplace_back并不完全兼容push_back,如emplace_back(  {"苹果", "apple"}  )就会报错

这是因为参数包class...Args在接收的时候会按照initializer_list来接收,传递也是如此,最后会按照initializer_list传递给pair的构造函数

而pair不能使用initializer_list来构造,所以不兼容。

这里容易混的是为什么不触发隐式类型转换,我们要搞清楚调用构造函数本身就是一层{},调用又是一层initializer_list,第二层的initializer_list就需要看构造函数支不支持了

相比之下,push_back接收的值就是pair<string, string>,而不是什么参数包,因此push_back(  {"苹果", "apple"}  )触发隐式类型转换,能够正常构造。我们也可以将{"苹果", "apple"}同样理解为initializer_list,它去匹配构造函数里面的参数。我们也可发现将{"苹果", "apple"}传给pair<string, string>ret就相当于pair<string, string>ret = {"苹果", "apple"},会以一层initializer_list去匹配参数。注意这里的initializer_list是传给对象而不是函数(构造函数自带一层{}),所以才能触发隐式类型转换

emplace_back也要考虑移动拷贝的情况,所以每次转发前要使用forward保证属性正确,防止右值变左值。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

三种方法加密图纸!2024如何对CAD图纸进行加密?分享给你

“机事不密则害成&#xff0c;是以君子慎密而不出也。” 此言道出了保密的重要性&#xff0c;尤其是在今日数字化时代&#xff0c;图纸作为设计领域的核心资料&#xff0c;其安全性更是至关重要。 CAD图纸作为设计行业的基石&#xff0c;不仅承载着设计师的心血与智慧&#x…

docker配置国内镜像加速

docker配置国内镜像加速 由于国内使用docker拉取镜像时&#xff0c;会经常出现连接超时的网络问题&#xff0c;所以配置Docker 加速来使用国内 的镜像加速服务&#xff0c;以提高拉取 Docker 镜像的速度。 1、备份docker配置文件 cp /etc/docker/daemon.json /etc/docker/da…

IT圈前端已死,后端快亡?这个职业却越来越缺人

前言 不知道何时&#xff0c;“前端已死&#xff0c;后端快完”的论调便充斥着整个互联网圈子&#xff0c;掘金&#xff0c;知乎&#xff0c;B站&#xff0c;牛客&#xff0c;脉脉…… 前端是什么&#xff1f; 前端通常指的是Web开发中与用户交互的部分&#xff0c;也称为客…

MyBatis核心机制

实现MyBatis核心机制环境搭建 1.核心框架示意图 2.模块搭建 1.创建maven项目 2.引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSc…

CoppeliaSim(V-Rep)与ROS1、ROS2接口变迁-2024-

Webots&#xff1a;Webots与ROS1、ROS2接口变迁 Gazebo&#xff1a;Gazebo与ROS1、ROS2接口变迁 ROS1 2016&#xff1a;ROS_Kinetic_18 使用V-Rep3.3.1和Matlab2015b&#xff08;vrep_ros_bridge&#xff09;续 vrep_ros_bridge 插件 一、项目背景与目标 vrep_ros_bridge 是…

Linux系统之jobs命令的基本使用

Linux系统之jobs命令的基本使用 一、jobs命令介绍二、jobs命令的使用帮助2.1 jobs命令的help帮助信息2.2 jobs命令的语法解释 三、jobs命令的基本使用3.1 运行一个后台任务3.2 列出后台所有的作业3.3 列出进程ID3.4 只列出进程ID3.5 终止后台任务3.6 只显示运行任务3.7 只显示停…

设备状态图表-甘特图

1.背景&#xff1a;设备状态监控图表&#xff0c;监控不同状态的时间段&#xff0c;可以使用甘特图来展示效果 鼠标经过时的数据提示框 2、代码实现 <template><divref"ganttChartRefs":style"{ height: 6.2rem, width: 100% }"class"bg…

视频项目开发,EasyCVR视频融合平台为何成为关键驱动力

智慧类视频项目是基于多个系统融合&#xff0c;旨在实现更广泛联动功能&#xff0c;以满足智能化应用需求为基石的信息化项目。当前&#xff0c;智慧社区、智慧园区、智慧工厂乃至智慧城市等应用场景的需求日益增长。这些智慧项目的整合进程中&#xff0c;视频融合能力扮演着不…

burpsuite xssValidator插件(xss插件)

安装 1. 商城安装插件 2. 安装环境 Download PhantomJShttps://phantomjs.org/download.htmlGitHub - NetSPI/xssValidator: This is a burp intruder extender that is designed for automation and validation of XSS

房价下跌的大环境下,我的佣金为何能逆市增长?

声明&#xff1a;此篇为 ai123.cn 原创文章&#xff0c;转载请标明出处链接&#xff1a;https://ai123.cn/2199.html &#x1f3e0;&#x1f4b0;最近房地产市场可不太景气&#xff0c;房价跌跌不休&#xff0c;咱们中介们的佣金收入也受到了影响。你知道那种心情吗&#xff1f…

【IoTDB 线上小课 06】列式写入=时序数据写入性能“利器”?

【IoTDB 视频小课】更新来啦&#xff01;今天已经是第六期了~ 关于 IoTDB&#xff0c;关于物联网&#xff0c;关于时序数据库&#xff0c;关于开源... 一个问题重点&#xff0c;3-5 分钟&#xff0c;我们讲给你听&#xff1a; 列式写入到底是&#xff1f; 上一期我们详细了解了…

汽车冷却液温度传感器的作用与检测方法

汽车冷却系统中的关键部件之一是冷却液温度传感器&#xff0c;它的位置通常在发动机的缸体或水泵附近&#xff0c;与冷却液直接接触。该传感器的作用是监测发动机冷却液的温度&#xff0c;它采用负温度系数热敏电阻&#xff0c;这种电阻随温度升高而降低。当冷却液温度达到预定…

AcWing 850. Dijkstra求最短路 II

迪杰斯特拉的优化算法采用堆优化&#xff0c;优化之前是花费 O ( n ) O(n) O(n)时间来查找未最优点里面距离点1最小的点。现在使用堆优化&#xff0c;直接花费 O ( 1 ) O(1) O(1)时间就完事儿了。 堆里面存储 p a i r < i n t , i n t > pair<int,int> pair<int…

Java毕业论文 【二手书电子商城网站】源码见github (原创项目,从0-1自己实现)

文章目录 项目背景主要功能模块分布模块分布具体部分功能 系统架构功能演示买家部分界面&#xff1a;卖家部分界面【8002模块】&#xff1a;管理员部分界面&#xff1a; 项目github地址 项目背景 主要面向高校学生&#xff0c;将高年级同学的书回收到低年级学生的手上&#xf…

CANoe.DiVa的应用——Diva进行诊断自动化测试执行过程详解(三)

🙋‍♂️【Vector CANdelastudio配置CDD】文章合集💁‍♂️点击跳转 ——————————————————————————————————–—— 从0开始学习CANoe使用 从0开始学习车载测试 相信时间的力量 星光不负赶路者,时光不负有心人。 目录 1.工程导入2.查看用…

什么是BOM,有哪些分类?

一、什么是BOM&#xff1f; BOM是物料清单的缩写&#xff0c;也称为产品结构表或产品结构树。 BOM的作用主要是通过计算机辅助企业生产管理&#xff0c;使计算机能够识别企业所制造的产品构成和所有要涉及的物料。 在制造业中&#xff0c;BOM是一份详细记录制造某个产品时所…

【算法基础实验】图论-最小生成树Kruskal实现

预备知识 【算法基础实验】图论-UnionFind连通性检测之quick-union_union find 判断是否成环-CSDN博客 【算法基础实验】排序-最小优先队列MinPQ_最小优先队列实现-CSDN博客 理论知识 Kruskal算法是一种用于查找加权无向图的最小生成树的贪心算法。最小生成树是一个连通的子…

uniapp使用live-pusher进行人脸识别打卡(安卓跟ios)

效果图 代码 使用live-pusher <live-pusher idlivePusher class"livePusher" mode"FHD" beauty"0" whiteness"0" aspect"9:16"min-bitrate"1000" audio-quality"16KHz" device-position"fron…

Apple pencil有替代品吗?2024开学季推荐五款实惠又好用的电容笔

如果只是用于学习和办工的话&#xff0c;Apple pencil是有替代品的&#xff0c;虽然Apple Pencil的性能不错&#xff0c;但它的高昂价格让很多用户不得不开始寻求性价比更高的平替电容笔。那么&#xff0c;在众多选择中&#xff0c;如何挑选一款合适的电容笔呢&#xff1f;以下…

【iOS】Block底层分析

目录 前言Block底层结构Block捕获变量原理捕获局部变量&#xff08;auto、static&#xff09;全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写…