C++11——2:可变模板参数

一.前言

C++11引入了可变模板参数(variadic template parameters)的概念,它允许我们在模板定义中使用可变数量的参数。这样,我们就可以处理任意数量的参数,而不仅限于固定数量的参数。

二.可变模板参数

我们早在C语言的学习过程中就接触过可变参数:printf和scanf都是可变参数的函数

他们的参数可以是1个,也可以是多个。其底层使用了一个二维数组将所有的参数存储起来。

对于之前的模板来说,都是固定参数个数的模板。C++11引入了可变参数的模板,即不仅参数的类型是不确定的,个数也是不确定的。支持可变参数的函数模板和类模板可变数目的参数被称为参数包,而参数包又分为两种:模板参数包和函数参数包

模板参数包:表示0~n个模板参数;函数参数包:表示0~n个函数参数。

template <typename ...Args> // 模板参数包
void Func(Args ...args) // 函数参数包
{}

// 左值引用
template <typename ...Args> // 模板参数包
void Func(Args& ...args) // 函数参数包
{}

// 万能引用——引用折叠
template <typename ...Args> // 模板参数包
void Func(Args&& ...args) // 函数参数包
{}

可变参数模板的语法规范是在定义模板参数列表的时候typename/class后面跟三个点...,然后写上模板参数名,这个名字是可以随便写的,但是为了规范使用Args更好一些。在定义函数形参表的时候类型名后面跟三个点...然后加上对象名。

函数参数包可以传值,传左值引用或者右值引用,跟普通的模板一样,这里也遵循引用折叠规则。

template <typename ...Args>
void Print(Args...args)
{
	cout << sizeof...(args) << endl;
}

int main()
{
	Print();
	Print(1);
	Print(1,3.14);
	Print(1,"1111111111",3,14);

	return 0;
}

对于上面这个Print函数,它支持可变模板参数,我们可以传0~n任意不同的参数。sizeof...可以计算参数包中的参数个数

那么我们在调用Print函数的时候到底发生了什么呢? 

编译器首先会根据我们调用该函数的实参的个数,生成有固定参数的函数模板,然后再根据实参的类型,实例化这些函数模板。

我们也可以反过来想一下:

阶段一:

        当没有模板语法的时候,我们想要实现可变参数的Print函数时,就要写多个函数,0个、1个、2个,n个,再根据实参类型不同,1个参数的函数也要写多个以适应不同的实参,2个、3个、n个的也一样。就很烦~

阶段二:

        当模板出来之后,我们不再需要写那些含有相同参数个数但参数类型不同的函数了,直接一个模板就可以了。这样对于1个参数的函数模板来说,任意类型的参数都可以传了,方便了很多。但是因为参数个数的不同,我们还是需要写很多个参数不同的函数模板。还是很烦~

阶段三:

        当可变模板参数出来了之后,我们也不再需要写多个参数个数不同的函数模板了,直接写一个可变参数的函数模板,他既解决了参数个数的不同,也解决了参数类型的不同。

而且,实参传的是左值还是右值也会影响实际生成的函数的样子,所以可变模板参数确实很方便。

虽然我们是这样分析可变模板参数调用的过程,但是在编译的过程中,编译器可能就不会有中间这步生成固定参数的函数模板,而是直接一步到位生成最终要调用的函数。

三.包扩展

对于一个参数包来说,我们除了计算它的参数个数之外,更多的情况下我们要去使用它,那么我们要怎么使用呢?我们能否在该函数内部直接使用for循环遍历该参数包呢?

当然是不行的了. 

 下面我们介绍两种包扩展的方式:

1.编译时递归包扩展

void ShowList()
{
	cout << endl;
}

template<typename T, typename...Args>
void ShowList(T&& x, Args&&...args)
{
	cout << x << endl;
	ShowList(args...);
}

template <typename ...Args>
void Print(Args&&...args)
{
	ShowList(args...);
}

我们通过这两个ShowList函数来实现包扩展。当我们在主函数调用Print函数的时候,实参会形成一个参数包,传给Print函数,Print函数内部无法直接解析包的内容,我们将参数包传给另一个函数,该函数也是一个函数模板,他会把参数包进行拆分:提取出参数包的第一个参数,然后打印该参数,然后自己调用自己将其他的参数作为参数包继续进行拆分。当参数包只剩一个参数的时候,T获取该参数,参数包变成空包,此时就会匹配那么无参数的ShowList,此时包扩展的过程完成。

由上述过程可以看出来,无参的ShowList起到了终止条件的作用。

该过程底层就类似于下图这个过程:

我们在调用Print函数的时候,编译器就会生成右边这几个函数。

那么我们是否可以将这个结束条件写道有参数的ShowList内部呢?

不行!

我们需要注意,我们上述包扩展的过程是编译时完成的,编译阶段,编译器对参数进行解析,生成若干个重载函数,实际上我们是调用那些重载函数来完成操作的。

而上图中if判断则是运行时逻辑,在程序运行的过程中,当参数包为空,递归结束。但是实际上,当参数包为空后,此时ShowList调用不到该函数本身,因为该函数本身必须接受一个参数T。

2. 通过返回值做函数参数包扩展

template<typename T>
const T& getArg(T&& x)
{
	cout << x << endl;
	return x;
}

template<typename...Args>
void Arguments(Args...args)
{}

template <typename ...Args>
void Print(Args&&...args)
{
	Arguments(getArg(args)...);
}

上面这种办法,我们在Print函数内部调用Arguments函数,但是调用该函数我们就得有实参,所以程序会先求该函数的实参,而实参是通过getArg函数解析参数包来获取的,所以我们也可以通过这种方式进行包扩展。

因为参数传参是从右往左的,所以这样解析参数包是反着解析的

四.emplace系列接口

1.emplace和push系列的对比

C++11引入了可变模板参数之后,STL的容器都引入emplace这一系列的接口。该系列的接口使用了可变模板参数,可以传任意类型和任意数量的参数。

那么emplace_back和push_back有什么区别呢? 我们接下来根据插入数据的类别来进行对比:

插入左值,push_back和empalce_back一样,都是拷贝构造。

插入左值push_back和emplace_back会进行引用折叠,最后都会调用左值引用版本,最后在函数内部,会new一个新节点,此时传给data的是一个左值引用,而data的类型是string,所以调用string的拷贝构造

插入右值,push_back和emplace_back一样,都调用移动构造。

插入右值,不会发生引用折叠,就会匹配对应的右值引用版本,在该函数内部,new一个新节点的时候,传给data的是一个右值,就是匹配data的移动构造

传匿名对象,push_back走隐式类型转换,先构造出临时对象,在函数内部引用该临时对象的调用移动构造;而emplace_back直接构造 

emplace_back之所以会直接构造,是因为emplace_back的参数类型是不确定的,只有当传参的时候才知道是const char*,所以在函数内部会直接调用string的构造函数来生成对象,list节点存储该对象。

当插入的是多参数的时候,规律和上面是一样的:

当插入左值时,两者都是拷贝构造;插入右值时,两者都是移动构造;插入匿名对象的时候,emplace_back是直接构造,而push_back是构造+移动构造

综上,在大部分情况下,push_back和emplace_back效率是一样的。但是在有些情况下,emplace_back直接构造,而push_back需要构造+移动构造(深拷贝的类型)或者构造+拷贝构造(浅拷贝的类型)。虽然移动构造的消耗很小,但毕竟有消耗;emplace_back和push_back真正的差异在于浅拷贝的类型,浅拷贝的类型没有移动构造,只能拷贝构造。

总的来说,emplace_back的效率整体上还是优于push_back的,所以以后用emplace系列的接口来替代push系列和insert系列的接口。

2.emplace的模拟实现

emplace_back就是可变模板参数类型的函数。但是在函数内部,我们没有必要对参数包进行扩展,我们并不在函数内使用参数,所以我们直接向下一层传递包即可。

template<typename...Args>
void emplace_back(Args...args)
{
	insert(end(), std::forward<Args>(args)...); // 直接向下转递参数包
}


完~ 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ENSP综合实验(中小型网络)

一、实验背景 在当今数字化的企业环境中&#xff0c;一个稳定、高效且安全的网络架构对于业务的持续运营和发展至关重要。随着企业内部各部门业务的不断拓展&#xff0c;如财务部门对数据保密性要求极高&#xff0c;访客区域的网络接入需求逐渐增多&#xff0c;以及对外提供特定…

nvidia控制面板找不到怎么回事?这有解决方法!

NVIDIA控制面板是一款用于管理和调整NVIDIA显卡的软件&#xff0c;它可以让你优化游戏和图形应用程序的性能和画质&#xff0c;以及设置多显示器、音视频、CUDA等功能。但是&#xff0c;有时候你可能会发现你的电脑上找不到NVIDIA控制面板&#xff0c;这可能是由于以下原因造成…

在Vue3项目中使用svg-sprite-loader

1.普通的svg图片使用方式 1.1 路径引入 正常我们会把项目中的静态资源放在指定的一个目录&#xff0c;例如assets,使用起来就像 <img src"../assets/svgicons/about.svg" /> 1.2封装组件使用 显然上面的这种方法在项目开发中不太适用&#xff0c;每次都需…

html+css+js网页设计 美食 美食3个页面(带js)

htmlcssjs网页设计 美食 美食3个页面(带js) 网页作品代码简单&#xff0c;可使用任意HTML辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&…

【235. 二叉搜索树的最近公共祖先 中等】

题目&#xff1a; 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一…

Visual Studio C++使用笔记

个人学习笔记 右侧项目不显示 CTRL ALT L 创建第一个项目 添加类&#xff08;头文件、CPP文件&#xff09;

【Shell脚本】Docker构建Java项目,并自动停止原镜像容器,发布新版本

本文简述 经常使用docker部署SpringBoot 项目&#xff0c;因为自己的服务器小且项目简单&#xff0c;因此没有使用自动化部署。每次将jar包传到服务器后&#xff0c;需要手动构建&#xff0c;然后停止原有容器&#xff0c;并使用新的镜像启动&#xff0c;介于AI时代越来越懒的…

vulhubn中potato靶场

IP和端口探测 80端口是一个图片 7120端口是这个 使用 hydra爆破密码 使用ssh远程登录 执行exp提权到root成功&#xff0c;找到Flag&#xff01;

复杂园区网基本分支的构建

目录 1、各主机进行网络配置。2、交换机配置。3、配置路由交换&#xff0c;进行测试。4、配置路由器接口和静态路由&#xff0c;进行测试。5、最后测试任意两台主机通信情况 模拟环境链接 拓扑结构 说明&#xff1a; VLAN标签在上面的一定是GigabitEthernet接口的&#xff0c…

信息科技伦理与道德2:研究方法

1 问题描述 1.1 讨论&#xff1f; 请挑一项信息技术&#xff0c;谈一谈为什么认为他是道德的/不道德的&#xff0c;或者根据使用场景才能判断是否道德。判断的依据是什么&#xff08;自身的道德准则&#xff09;&#xff1f;为什么你觉得你的道德准则是合理的&#xff0c;其他…

git理解记录

文章目录 1. 背景2. 基本概念3. 日常工作流程4. 其他常见操作4.1 merge合并操作4.2 tag打标签操作4.3 remoute远程操作4.4 撤销修改 git理解记录 1. 背景 git作为分布式版本控制系统&#xff0c;开源且免费&#xff0c;相比svn集中式版本控制系统存在速度快(HEAD指针指向某次co…

【连续学习之LwM算法】2019年CVPR顶会论文:Learning without memorizing

1 介绍 年份&#xff1a;2019 期刊&#xff1a; 2019CVPR 引用量&#xff1a;611 Dhar P, Singh R V, Peng K C, et al. Learning without memorizing[C]//Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2019: 5138-5146. 本文提…

使用Paddledetection进行模型训练【Part1:环境配置】

目录 写作目的 安装文档 环境要求 版本依赖关系 安装说明 写作目的 方便大家进行模型训练前的环境配置。 安装文档 环境要求 PaddlePaddle &#xff1e;&#xff1d;2.3.2OS 64位操作系统Python 3(3.5.1/3.6/3.7/3.8/3.9/3.10)&#xff0c;64位版本pip/pip3(9.0.1)&am…

【51单片机-零基础chapter1】

安装软件(配套的有,不多赘述) 1.管理员身份运行keil和破解软件kegen 将CID代码复制粘贴到 一定要管理员方式,不然会error 插入板子 我的电脑,管理 1.如果是拯救者,查看端口,如果没有则显示隐藏 2.苹果不知道,好像不可以 3.其他电脑在"其他设备找" (注:本人在校已…

现代密码学期末重点(备考ing)

现代密码学期末重点&#xff0c;个人备考笔记哦 密码学概念四种密码学攻击方法什么是公钥密码&#xff1f;什么是对称密码&#xff1f;什么是无条件密码&#xff1f; 中国剩余定理&#xff08;必考&#xff09;什么是原根什么是阶 经典密码学密码体制什么是列置换&#xff1f; …

xinput1_3.dll丢失的解决之道:简单易懂的几种xinput1_3.dll操作方法

在计算机系统和游戏领域中&#xff0c;xinput1_3.dll是一个备受关注的动态链接库文件。它在游戏输入设备的支持和交互方面发挥着至关重要的作用。接下来&#xff0c;我们将详细探讨xinput1_3.dll的各种属性。 一、xinput1_3.dll文件的常规属性介绍 xinput1_3.dll文件名 xinpu…

2025-01-06 Unity 使用 Tip2 —— Windows、Android、WebGL 打包记录

文章目录 1 Windows2 Android2.1 横版 / 竖版游戏2.2 API 最低版本2.3 目标帧率2.3.1 targetFrameRate2.3.2 vSyncCount2.3.3 Unity 默认设置以及推荐设置2.3.4 Unity 帧率托管 3 WebGL3.1 平台限制3.2 打包报错记录 13.3 打包报错记录 2 ​ 最近尝试将写的小游戏打包&#xff…

Deep blind super-resolution for hyperspectral images_译文

关键词&#xff1a; 高光谱图像 盲超分辨率 退化模型 深度学习 摘要 目前单张高光谱图像超分辨率的深度学习方法都是非盲方法&#xff0c;采用简单的双三次退化模型。这些模型泛化性能较差&#xff0c;无法处理未知的退化。此外&#xff0c;RGB图像的盲超分辨率方法忽略了高光…

Visual studio code编写简单记事本exe笔记

安装扩展cmake tools c/c c/c Extension pack CMakeLists.txt cmake_minimum_required(VERSION 3.20) project(NotepadApp)set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)# Windows specific settings if(WIN32)set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)s…

Java100道面试题

1.JVM内存结构 1. 方法区&#xff08;Method Area&#xff09; 方法区是JVM内存结构的一部分&#xff0c;用于存放类的相关信息&#xff0c;包括&#xff1a; 类的结构&#xff08;字段、方法、常量池等&#xff09;。字段和方法的描述&#xff0c;如名称、类型、访问修饰符…