C++ - 可变模版参数 - emplace相关接口函数 - 移动构造函数 和 移动赋值运算符重载 的 默认成员函数
可变模版参数
我们先来了解一下,可变参数。可变参数就是在定义函数的时候,某一个参数位置使用 "..." 的方式来写的,在库当中有一个经典的函数系列就是用的 可变参数:printf()系列就是用的可变参数:
如上图所示,printf()函数的第二个参数就是 可变参数(注意:可变参数 "..." 语法上在之前必须要有一个参数)。那么 第二个参数位置,就可以写很多个参数,在printf()函数内部就可以把第二参数位置的这些多个参数 解析出来。底层其实是用一个数组,把可变参数位置传入的实参接收的,printf()内部就会去访问这个数组,把这些参数取出来。
在 C++ 当中就需要有 可变的模版参数了。
我们说模版参数和 函数参数其实是很类似的:
- 模版参数 是 传递类似;
- 函数参数 是 传递对象;
那么 可变的模版参数其实就是传入多个类型,想要几个类型, 就传入几个类型。
可变模版参数的语法
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
在可变函数参数之后,要加上 Args ,这个 Args 其实是一个 模版参数包,而 在函数当中 args 是一个函数形参参数包,两者是不一样的,要注意区分。
当然,Args 这个名字可以自定义,想取什么名字都是可以的,但是我们一般命名为 Args。其实 Args 就是 这个可变参数的 模版参数名称,跟之前取 T, K 都是一样的。
如上图所示,在函数当中的 args 函数形参参数包,是用 Args 模版参数包构建出来的,如果 Args 当中只有一个类型,那么 args 当中就只会构建出一个形参,类比,如果 Args 当中只有两个类型,那么 args 当中就只会构建出两个形参。
可变参数包在函数当中的使用
那么,我们知道了 如果定义一个 可变模版参数,和 在函数参数列表当中传入 这个 可变模版参数,那么这个可变模版参数在函数当中如果使用呢?
template <class T, class ...Args>
void ShowList(T value, Args... args)
{}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}
如上面这个例子,在 ShowList()这个函数模拟,到底会实例化出多少个 函数出来,我们可以在函数内部使用 sizeof()对 args 函数形参参数包 进行 大小的打印,就可以知道,当前实例化出来的 函数,有多少个参数了:
void ShowList(T value, Args... args)
{cout << sizeof(args) << endl;
}
但是,直接像上述一样计算 args 的大小是不行的,编译器不给这样玩,我们需要在 sizeof和 (之前加上 "..." 才行;
上述编译报错:
“args”: 必须在此上下文中扩展参数包
得像下面一样写:
void ShowList(T value, Args... args)
{cout << sizeof...(args) << endl;
}
输出:
0
1
2
那么,有人就像了,既然args 实现跟数组类似,那么我们可不可以直接取出 args 当中的数据呢?
答案是不行的,它不给直接取出args 的数据,必须要扩展上下文(具体在后面阐述):
编译报错:
“args”: 必须在此上下文中扩展参数包
利用模版参数的推演,取出 参数包当中的值
对于 扩展参数包,除了可以像上述一样 在前置加 "..." 的方式,扩展 参数包;
我们还可以利用编译器对模版参数的推演,来取出参数包当中的类型。
具体做法就是在 可变模版参数之前,多加一个 模版参数,利用编译器对这个模版参数的推演,扩展出 args 当中的类型:
// 递归终止函数
template <class T>
void ShowList(const T& t)
{// 编译时递归推导的// 结束时候的函数cout << t << endl;
}// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}
如果之传入的一个参数,那么直接调用 第一个 ShowList(const T& val);如果传入的是多个参数,那么就会调用 void ShowList(T value, Args... args) 这个函数,在这个函数当中,就会把第一个参数 推导出来,打印,然后又去调用 void ShowList(T value, Args... args) 这个函数,但是在传入参数的时候值传入 参数包,相当于是把 第一个参数舍弃了,然后传参,这样再下一层函数,就会从参数包当中取出第一个数据。
直到,参数包当中数据区得只剩下 一个,那么就会调用 第一个 结合条件的函数。相当于是 利用 第一个模版参数 一直推导出 args 当中的类型。
但是,可变模版参数很少用到,上诉过程可以制作理解。
但是上述取出 args 的方法只能取出有参数的 例子,如果是无参传入的话,就会不匹配了,所以我们对结束函数进行改进,把结束函数改为 无参的 函数:
void ShowList()
{cout << endl;
}
还有一种新的方式:
template <class T>
void PrintArg(T t)
{cout << t << " ";
}//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。
由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
如果不想用 逗号表达式的话,可以利用 printarg()函数的返回值,返回一个 0:
template <class T>
int PrintArg(T t)
{cout << t << " ";return 0;
}//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { PrintArg(args)... };cout << endl;
}
empalce相关接口函数
我们先来看一个 可变模版参数的引用场景:
class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Date构造" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date拷贝构造" << endl;}private:int _year;int _month;int _day;
};template <class ...Args>
Date* Create(Args... args)
{Date* ret = new Date(args...);return ret;
}int main()
{Date* p1 = Create();Date* p2 = Create(2023);Date* p3 = Create(2023, 9);Date* p4 = Create(2023, 9, 27);Date d(2023, 1, 1);Date* p5 = Create(d);return 0;
}
当 Data 当中的构造函数是这样写的时候:
Date(int year , int month , int day ) -> 外部函数当中传入三个参数:Date* p4 = Create(2023, 9, 27);
也就是,传入三个参数,那么 在 Create 当中的 参数包就会接受这三个参数,然后传入到 Data 构造函数当中;
如果传入的不是 三个参数( Date* p1 = Create(); Date* p2 = Create(2023); Date* p3 = Create(2023, 9);),就会报错。
但是,在上述例子当中吗,我们是吧 data 的构造函数当中的三个 参数都些都写成是 缺省的,所以在上述例子的当中才可以那么自由的使用 Create ()函数。
STL容器当中,还有 empalce相关接口函数:
cplusplus.com/reference/vector/vector/emplace_back/
cplusplus.com/reference/list/list/emplace_back/
template <class... Args>
void emplace_back (Args&&... args);
首先我们看到的emplace系列的接口,支持模版的可变参数,并且,函数当中的形参是万能引用,那么 emplace_back()接口相对于 insert()有那些提升呢?
int main()
{std::list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
push_back ()函数,需要传入一个 make_pair ()构造一个 pair 对象传入,才能进行尾插;但是 在 emplace_back()接口,就直接传入参数,它会被 参数包接受,然后在 解析出来。但是,上述除了 在用法上不同,实际上和 push_back()是差不多的。
int main()
{// 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back// 是先构造,再移动构造,其实也还好。std::list< std::pair<int, bit::string> > mylist;mylist.emplace_back(10, "sort");mylist.emplace_back(make_pair(20, "sort"));mylist.push_back(make_pair(30, "sort"));mylist.push_back({ 40, "sort" });return 0;
}
也就是说, emplace_back()能做到,直接引用参数来对其中的结点进行构造;但是 push_back()必须先进行构造,然后在进行拷贝构造。
按照上述来看,emplace_back()还是有优化的,但是别忘了 ,C++11 之后是可以使用 移动拷贝的,想上述 push_back()先构造,在进行拷贝构造,就会被直接优化为 空间的 两个指针的交换,这个代价就太低了,相对来说,emplace_back()就不够看了。
移动构造函数 和 移动赋值运算符重载 的 默认成员函数
在C++ 11之前,有 6 大默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
C++11 增加有值引用之后,就有了移动语义的概念,所谓移动语义就是 移动构造函数 和 移动赋值运算符重载函数,两函数。
那么所以默认成员函数的话,我们写了,那好说,就使用我们定义的 函数;如果我们没有写,那么编译器就会自动生成一个,这个自动生成是什么条件下的才会自动生成的?
- 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
这里和 之前默认成员函数一样,对于浅拷贝(也就是只拷贝内置类型),那么我们可以放心大胆的交给编译器去自动生成,就够用了;如果是深拷贝(比如自己开得有 堆空间),那么就需要我们自己定义 移动构造函数 和 移动赋值运算符重载函数。
如果我们都实现析构函数 、拷贝构造、拷贝赋值重载 但是我们又想编译器自己生成一个 移动构造函数 或者是 移动赋值重载函数 的话,我们可以这样写:
string(Person&& p) = default;
上述只给出了 移动构造函数 的方式,移动赋值重载函数 的方式也是类似的。
这样可以让编译器前置生成一个 移动构造函数 或者是 移动赋值重载函数
相关文章:

C++ - 可变模版参数 - emplace相关接口函数 - 移动构造函数 和 移动赋值运算符重载 的 默认成员函数
可变模版参数 我们先来了解一下,可变参数。可变参数就是在定义函数的时候,某一个参数位置使用 "..." 的方式来写的,在库当中有一个经典的函数系列就是用的 可变参数:printf()系列就是用的可变参…...

总结三:计算机网络面经
文章目录 1、简述静态路由和动态路由?2、说说有哪些路由协议,都是如何更新的?3、简述域名解析过程,本机如何干预域名解析?4、简述 DNS 查询服务器的基本流程是什么?DNS 劫持是什么?5、简述网关的…...

服务器数据恢复-VMWARE ESX SERVER虚拟机数据恢复案例
服务器数据恢复环境: 几台VMware ESX SERVER共享一台某品牌存储,共有几十组虚拟机。 服务器故障: 虚拟机在工作过程中突然被发现不可用,管理员将设备进行了重启,重启后虚拟机依然不可用,虚拟磁盘丢失&#…...

COCI 2021-2022 #1 - Set 题解
思路 我们将原题中的数的每一位减一,此时问题等价。 下面的异或都是在三进制下的异或。(相当于不进位的加法) 我们考虑原题中的条件,对于每一位,如果相同,则异或值为 0 0 0,如果为 1 1 1&a…...

分享40个极具商业价值的chatGPT提问prompt
原文:分享40个极具商业价值的chatGPT提问prompt | 秋天的童话博客 1、分析并改善定价策略 提示: "分析我当前的[插入产品或服务]定价策略。提出改进建议,并帮助我制定新的定价策略,以最大化利润和客户满意度。" Analyze and Imp…...

一文搞懂到底什么是元宇宙
一、背景 2021年,“元宇宙”是科技界的开端。 2021”元宇宙”这个词在Facebook更名后被点燃了,无疑是21世纪科技界最爆的起点。各式各样的定义、解读都出现了,有人说它是炒作,甚至是骗局,但也有人说它就是互联网的未…...

【重拾C语言】六、批量数据组织(四)线性表—栈和队列
目录 前言 六、批量数据组织——数组 6.1~3 数组基础知识 6.4 线性表——分类与检索 6.5~7 数组初值;字符串、字符数组、字符串数组;类型定义 typedef 6.8 线性表—栈和队列 6.8.1 栈(Stack) 全局变量 isEmpty() isFull…...

力扣刷题-哈希表-一个字符串是否能够由另一个字符串中的字符组成
383 赎金信 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以,返回 true ;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。ransomNote 和 magazin…...

Android使用AOP切面编程
在Android应用程序中,AOP可以被用于许多不同的场景,例如日志记录、权限控制、性能分析等。下面是一个简单的例子,说明如何在Android应用程序中使用AOP切面编程。 首先,我们需要在应用程序中引入AspectJ库。我们可以使用Gradle来完…...

Flutter学习笔记
此篇文章用来记录学习Flutter 和 Dart 相关知识 零.Dart基本数据类型 Dart 是一种静态类型的编程语言,它提供了一系列基本数据类型,用于存储和操作不同种类的数据。以下是 Dart 中的一些基本数据类型以及它们的详细介绍: 1. 整数类型&#…...

软件生命周期中的概念设计和详细设计的主要任务是什么
基础概念 在软件生命周期中,概念设计和详细设计是软件设计阶段中的两个重要环节。 概念设计阶段的主要任务是从业务需求出发,将系统的基本概念、主要功能和关键特性进行抽象和定义。概念设计旨在确定系统的整体架构和关键模块,包括以下主要…...

大数据学习(2)Hadoop-分布式资源计算hive(1)
&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博>主哦&#x…...

深入探究HTML表单与JavaScript的关系
深入探究HTML表单与JavaScript的关系 引言 HTML表单是网页中数据收集的重要工具,而JavaScript则充当着这些数据的处理者和控制者的角色。二者之间的关系非常紧密,共同构成了现代Web应用中用户交互的基础。在这篇博客中,我们将详细地解析HTM…...

关于Jupyter notebook 创建python3 时进去不能重命名问题及不能编程问题
首先写这篇博客时,已经被这个问题折磨了三天,看了很多博客,其实解决这个问题的关键就是要么没有下pyzmq或者等级太高,要么等级太低,首先我会按照我思路来。 问题如图: 1.自动换行 2.不能重命名 我的解决办…...

一些可以用代码绘制流程图的工具
代码绘制流程图的工具有很多,以下是一些常用的工具: Mermaid:Mermaid 是一个基于 Markdown 的图表语言,可以生成各种类型的图表,包括流程图、时序图、甘特图等。Mermaid 可以使用 JavaScript 或 TypeScript 进行编写,可以通过 Node.js 运行。Graphviz:Graphviz 是一个开…...

Centos中清除因程序异常终止,导致的残留的Cache/buff_drop_caches命令---linux工作笔记063
我这里因为nifi程序背压设置的不合理,导致,内存和CPU消耗过高,系统崩溃,但是重启NIFI以后,发现 对应的执行top命令,看到,系统的buff/cache 依然没有减少,说明内存被浪费了,残留在这里没有被回收. 用这个办法执行这个命令; linux会自动触发清理,但是只有在内存不够用的时候才会…...

Element-UI的使用——表格el-table组件去除边框、滚动条设置、隔行变色、去除鼠标悬停变色效果(基于less)
// Element-ui table表格去掉所有边框,如下: // 备注:若去掉所有边框,可自行将头部边框注释掉即可 // 该样式写在style scoped外面在el-table 中添加class"customer-table"类名 //去掉每行的下边框/deep/ .el-table td.el-table__c…...

python的一些知识点
之前自学过python,学了一些基本语法,但忘得厉害。最近,在努力地写代码,在学代码,写代码中学习python,为此记录一些关于python的知识点。...

QML 带框最大化显示方法
1.QML窗口最大化很多会给出如下方法: visibility: "FullScreen" 此方法不好的方面是没有最大化,最小化,关闭按钮 2.通过showMaximized() 方法可以满足我们需求:在onCompleted 方法中执行 实现的效果如下:...

conda命令大全
conda list 查看环境中已经安装了的软件包 conda list --name your_env_name(虚拟环境名) 查看某个环境下的包 conda config --show 查看现有源 conda env list 或者 conda info -e 查看当前存在那些虚拟环境 conda update conda 更新至最新的conda版本 conda update [pac…...

国庆要闻回顾 | OpenAI 拟研发 AI 手机;9月以太坊上NFT销售量创2021年2月以来最低记录...
国庆期间区块链行业要闻回顾:产业方面,全国区块链行业产教融合共同体在雄安新区成立,巴西推出基于区块链的数字身份证,瑞银集团在以太坊上推出代币化货币市场基金试点,NASA拟在月球设立区块链数据中心以保存国家机密资…...

国家开放大学 模拟试题 训练
试卷代号:2136 管理会计 参考 试题 一、单项选择题(每小题1分,共20分) 1.管理会计依靠各种功能来助力企业战略,下列哪项是管理会计的核心功能( )。 A.评价功能 B.预测功能 C.决策功能…...

【GIT版本控制】--常见问题与解决方案
一、修复损坏的仓库 修复损坏的Git仓库可能是面临的一种问题,这通常是由于文件损坏、存储介质问题或不正确的操作等原因引起的。以下是一些修复损坏的Git仓库的常见问题和解决方案: 常见问题: 无法执行Git命令:当尝试运行Git命令…...

Redis安装及key、string操作
安装 在官网下载的数据包上传到Linux家目录 Install Redis from Source | Redis wget https://download.redis.io/redis-stable.tar.gz tar -xzvf redis-stable.tar.gz cd redis-stable make 编译后出现以下提示后输入make install 出现以下提示则安装成功 输入redis-sever启…...

TCP和UDP的由浅到深的详细讲解
目录 前言 一.TCP 1.1 什么是TCP? 1.2TCP的连接与释放(确认应答机制) 1.2.1三次握手 1.2.2四次挥手 1.3TCP滑动窗口(效率机制) 1.4流量控制(安全机制) 1.5拥塞控制(安全机制࿰…...

端粒/端粒酶生信切入点,6+端粒酶+泛癌+甲基化+实验。
今天给同学们分享一篇端粒酶泛癌甲基化实验的生信文章“Genomic, epigenomic, and transcriptomic signatures for telomerase complex components: a pan‐cancer analysis”,这篇文章于2022年10月31日发表在Mol Oncol期刊上,影响因子为6.6。 激活端粒酶…...

XMLHttpRequest和Fetch API
XMLHttpRequest和Fetch API 1、XMLHttpRequest2、Fetch API总结 简述:XMLHttpRequest和Fetch API是两种常用的JavaScript网络请求方式,可以用来发送HTTP请求并获取服务器响应。 1、XMLHttpRequest XMLHttpRequest:XMLHttpRequest是一种传统的…...

U-boot下netconsole实现
U-boot下netconsole实现 参考1.函数main_loop和u-boot命令执行 上面的do-while会循环命令解析器的"命令输入解析–执行"运行模式。 其中的函数run_list执行如下的函数调用流程:run_list–>run_list_real–>run_pipe_real->cmd_process 源码跟读…...

Unity设计模式——原型模式
原型模式(Prototype)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需知道任何创建的细节 。 原型类 Prototype: abstract class P…...

leetcode 96 不同的二叉搜索树
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n 3 输出:5 示例 2: 输入:n 1 输出:1…...