【C++】C++11新特性详解:可变参数模板与emplace系列的应用

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用
String模拟实现Vector使用及其模拟实现List使用及其模拟实现容器适配器Stack与QueuePriority Queue与仿函数
模板进阶-模板特化面向对象三大特性-继承机制面向对象三大特性-多态机制STL 树形结构容器二叉搜索树
AVL树红黑树红黑树封装map/set哈希-开篇闭散列-模拟实现哈希
哈希桶-模拟实现哈希哈希表封装 unordered_map 和 unordered_setC++11 新特性:序章右值引用、移动语义、万能引用实现完美转发

大家好,我是店小二。在这篇文章中,我们将深入探讨C++11的新特性——可变参数模板和emplace系列的应用。如果在阅读过程中有不清楚的地方或发现任何错误,欢迎随时私信交流探讨。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 一、新的类功能
    • 1.1 移动语义注意项
    • 1.2 Rule of Five机制
    • 1.3 小结
  • 二、"强制生成"默认函数的关键字default
  • 四、"禁止生成"默认函数的关键字delete
  • 三、可变参数模板
    • 3.1 基本可变参数的函数模板
    • 3.2 获得参数包的值
      • 3.2.1 不支持使用args[i]
      • 3.2.2 递归函数方式展开参数包
      • 3.2.3 逗号表达式展开参数包
  • 四、emplace系列(尽量配合参数包)
    • 4.1 empalce系统的优势
    • 4.2 emplace使用推荐

一、新的类功能

在C++11前,C++类有六个默认成员函数(默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数 )

在这里插入图片描述

1.1 移动语义注意项

C++11 新增了两个:移动构造函数和移动赋值运算符重载 。在关于右值引用篇章有相关介绍:右值引用与移动语义

如果没有显式实现移动构造或赋值函数,同时没有显式显式析构、拷贝、赋值重载函数中任意一个。编译器会自动生成默认移动构造。其中默认生成的移动构造或赋值函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员(注意是成员),则需要看这个成员是否实现移动构造或赋值,如果实现了就调用移动构造或赋值,没有实现就调用拷贝构造或赋值重载。

1.2 Rule of Five机制

C++11 引入了五个特殊成员函数,其中有三对:

  1. 拷贝构造函数拷贝赋值运算符
  2. 移动构造函数移动赋值运算符
  3. 析构函数

当你定义其中的一个(如移动构造函数),编译器会认为你对这个类的资源管理有特殊的要求,因此不再生成默认的拷贝构造函数和拷贝赋值运算符,避免误用浅拷贝导致资源管理错误

析构函数和移动构造函数的不同角色:

  • 析构函数:用于销毁对象并释放其占用的资源。显式定义析构函数意味着你要自行控制资源的释放方式。C++ 假定你手动管理资源,因此不会为你生成其他依赖于默认资源管理的函数(如移动构造函数)
  • 移动构造函数:用于将资源从一个对象转移到另一个对象。它不负责销毁对象,而是将对象的资源"转交"给另一个对象。

移动构造函数主要是负责“转移”资源,而不是释放资源,编译器假设转移资源并不改变析构时的行为,所以它会继续生成默认析构函数,认为默认的资源释放机制(如自动销毁对象的成员)依然有效。对此当显示实现移动构造函数,编译器也会自动生成默认的析构函数,确保资源的销毁。

1.3 小结

析构函数、拷贝构造、拷贝赋值重载是对于容器中深拷贝的类关于资源的管理,为了避免潜在的资源管理问题和不一致性,当你显式定义了析构函数时,编译器会尊重你的选择,不再生成默认的移动构造函数,需要你根据具体的类设计和资源管理策略,决定是否需要自定义移动构造函数(这三个特殊成员函数之间存在依赖的关系Rule of Five机制)。

// 以下代码在vs2013中不能体现,在vs2019下才能演示体现上面的特性。
class Person
{public:Person(const char* name = "", int age = 0):_name(name), _age(age){}/*Person(const Person& p)
:_name(p._name)
,_age(p._age)
{}*//*Person& operator=(const Person& p)
{
if(this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}*//*~Person()
{}*/private://自定义成员bit::string _name;//内置类型成员int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

在这里插入图片描述

需要注意,关于移动语义是一种夺舍的行为,需要考虑被夺舍对象是否需要使用原本的资源,进行调用。

二、"强制生成"默认函数的关键字default

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

特殊函数之间可能存在依赖或互斥的关系,编译器不会随意地插入可能与用户意图不符的代码,也就导致了当强制生成移动语句,编译器不会默认生成析构函数等与之依赖性强的函数,如果需要移动语句和拷贝函数等函数同时出现,建议全部进行强制生成。

在这里插入图片描述

因为自己去写的话,还是麻烦了一点,关于这些问题可以看成一个语法规定就好了。

四、"禁止生成"默认函数的关键字delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

如以下场景,这里类只能在堆上生成对象

class HeapOnly
{public:static HeapOnly* CreateObj(){return new HeapOnly;}//C++11HeapOnly(const HeapOnly&) = delete;//C++98 私有+只声明不实现private:HeapOnly(const HeapOnly&);HeapOnly(){}int _a = 1;
};
int main()
{//HeapOnly ho1;//HeapOnly* p1 = new HeapOnly;//以上是构造函数私有HeapOnly* p2 = HeapOnly::CreateObj();//尝试在堆上开辟空间// 不能被拷贝,才能禁止//HeapOnly obj(*p2);return 0;
}

分析几行代码:

  1. HeapOnly* p1 = new HeapOnly;这里构造函数是私有的
  2. HeapOnly obj(*p2);不能被拷贝,禁止拷贝构造函数,也是栈上开空间
  3. HeapOnly* p2 = HeapOnly::CreateObj();通过静态工厂方法在堆上创建对象

三、可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。

3.1 基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。(可以自动推导类型)
template <class ...Args>void ShowList(Args... args)
{}

3.2 获得参数包的值

3.2.1 不支持使用args[i]

参数args前面有省略号,所以它就是一个可变模板参数;将带有省略号的参数称为参数包,它里面包含了0到N(N >= 0)个模板参数。

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值

template<class ...Args>void Cpp_Printf(Args... args)
{cout << sizeof...(args) << endl;// error C3520: “args”: 必须在此上下文中扩展参数包// 不支持for (size_t i = 0; i < sizeof...(args); i++){cout << args[i] << endl;}cout << endl;
}int main()
{Cpp_Printf(1,'A',"sort");return 0;
}

使用 args[i] 这样的写法在编译时会导致错误,因为模板参数包 args 并不是一个数组,不能使用索引访问。

3.2.2 递归函数方式展开参数包

**参数包中的参数类型确定需要在编译时确定,**这意味着不能在运行时动态推断参数包中每个参数的具体类型,而在递归中模板推导参数类型是在编译时进行的。

void ShowList ()
{cout << 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;
}

在调用函数时,第一个参数必须能够匹配到 T 类型Args... args:这是一个参数包,用来接收除了第一个参数 value 之外的所有剩余参数。因此可以通过模板的递归展开,每次处理一个参数,并递归地处理剩余的参数,知道没有参数需要处理为止。

在这里插入图片描述

3.2.3 逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式
实现的关键是逗号表达式。

具体说明:

  1. 这里主要是利用了参数包展开的特性及其列表初始化会进行遍历初始化。这里(PrintArg(args), 0),按照顺序执行逗号表达式,先执行PrintArg(args),返回结果为0,用于数组元素存储。
  2. 通过列表初始化特性, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0),(printarg(arg3),0), etc... ),最终会创建一个元素值都为0(逗号表达式,取最后的值)的数组int arr[sizeof... (Args)]
  3. 由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包
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;
}

四、emplace系列(尽量配合参数包)

STL容器(string不支持)中emplace相关接口函数:
在这里插入图片描述

在这里插入图片描述

template <class... Args>void emplace_back(Args&&... args)

emplace系列的接口支持模板的可变参数和万能引用。

4.1 empalce系统的优势

那么相对于insert和emplace系列接口的优势到底在哪里呢?

在这里插入图片描述

乍一看无论是使用左值还是右值,感觉insert和emplace没啥区别啊!这里没有体现出可变参数包的作用,那么再通过一个例子就行更加深入了解。

在这里插入图片描述

如果是emplace_back还是单纯的同push_back传递pair对象,那么也没有多大差别。如果是按照蓝色框框传给参数包或直接传递参数,那么emplace系列作用得以体现。注意这里模板推导出来,不要将模板和模板推导函数混在一起。

在这里插入图片描述
在这里插入图片描述

直接传递pair的参数包,参数包一直往下传,底层直接构造。这里建议大家使用emplace系列更加高效(不一定高效,需要分场合)。

4.2 emplace使用推荐

emplace系列函数在C++中用于在容器构造对象,而不是拷贝现有对象。它们通常与可变参数模板一起使用,以便于直接在容器内部就地拷贝对象,而不是通过拷贝构造函数或移动构造函数进行操作

个人理解:emplace传左值,在传参过程中会调用拷贝构造,对于右值,万能引用会推出右值,使用右值引用接收,没有拷贝构造的调用

  • 直接emplace 或 push/insert 左值 —> 构造 + 移动构造

  • 直接emplace 参数包—> 构造

  • 有移动构造深拷贝对象,差别不大,由于移动构造的代价很小

  • 直接emplace 或 push/insert 右值/浅拷贝右值对象 —> 构造 + 拷贝构造

  • 直接emplace 参数包—> 构造

  • 代价就大了很多,由于拷贝构造代价很大,没有移动构造浅拷贝的对象,区别也比较大

对此以后使用容器接入接口,推荐emplace系列,push系列/insert的接口,推荐使用emplace系列代替,其次emplace能用参数包就用参数包。就是构造函数中_data(s1)和 _data(“11”)会导致什么后果。

在这里插入图片描述


在这里插入图片描述

以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!

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

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

相关文章

多模态大型语言模型(MLLM)综述

目录 多模态大语言模型的基础 长短期网络结构(LSTM) 自注意力机制 基于Transformer架构的自然语言处理模型 多模态嵌入的关键步骤 TF-IDF TF-IDF的概念 TF-IDF的计算公式 TF-IDF的主要思路 TF-IDF的案例 训练和微调多模态大语言模型(MLLM) 对比学习 (CLIP, ALIG…

Otter 安装流程

优质博文&#xff1a;IT-BLOG-CN 一、背景 随着公司的发展&#xff0c;订单库的数据目前已达到千万级别&#xff0c;需要进行分表分库&#xff0c;就需要对数据进行迁移&#xff0c;我们使用了otter&#xff0c;这里简单整理下&#xff0c;otter 的安装过程&#xff0c;希望对…

Web3 游戏周报(11.17 - 11.23)

回顾上周的区块链游戏概况&#xff0c;查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【11.17 - 11.23】Web3 游戏行业动态&#xff1a; 加密游戏开发商 Gunzilla Games 发推表示&#xff0c;其已与 Coinbase Ventures 达成合作并获得其投资。 国际足联将与 Mythica…

【linux学习指南】初识Linux进程信号与使用

文章目录 &#x1f4dd;信号快速认识&#x1f4f6;⽣活⻆度的信号&#x1f4f6; 技术应⽤⻆度的信号&#x1f309; 前台进程&#xff08;键盘&#xff09;&#x1f309;⼀个系统函数 &#x1f4f6;信号概念&#x1f4f6;查看信号 &#x1f320; 信号处理&#x1f309; 忽略此信…

3DEXPERIENCE软件是干什么的—3DE软件代理商微辰三维

在当今数字化转型浪潮席卷全球各个行业的大背景下&#xff0c;3DEXPERIENCE 软件宛如一颗璀璨的明星&#xff0c;闪耀在产品设计、制造以及协同创新等诸多领域。它是由达索系统公司推出的一款综合性的、功能强大的商业软件平台&#xff0c;为企业的整个产品生命周期管理带来了前…

【大数据学习 | Spark-Core】广播变量和累加器

1. 共享变量 Spark两种共享变量&#xff1a;广播变量&#xff08;broadcast variable&#xff09;与累加器&#xff08;accumulator&#xff09;。 累加器用来对信息进行聚合&#xff0c;相当于mapreduce中的counter&#xff1b;而广播变量用来高效分发较大的对象&#xff0c…

STM32编程小工具FlyMcu和STLINK Utility 《通俗易懂》破解

FlyMcu FlyMcu 模拟仿真软件是一款用于 STM32 芯片 ISP 串口烧录程序的专用工具&#xff0c;免费&#xff0c;且较为非常容易下手&#xff0c;好用便捷。 注意&#xff1a;STM32 芯片的 ISP 下载&#xff0c;只能使用串口1&#xff08;USART1&#xff09;&#xff0c;对应的串口…

MTK主板_安卓主板方案_MTK联发科主板定制开发

联发科(MTK)主板以其强大的性能和多样化的功能而受到广泛关注。该平台包括多个型号&#xff0c;例如MT6761、MT8766、MT6762、MT6765、MT8768和MT8788等&#xff0c;均配置了四核或八核64位处理器&#xff0c;主频可高达2.0GHz。采用先进的12nm工艺&#xff0c;搭载Android 11.…

信息收集(1)

学习视频引路信息收集&#xff08;1&#xff09;_哔哩哔哩_bilibili View信息收集&#xff08;1&#xff09; 分享一个漏洞挖掘平台&#xff1a;补天 以吉林通用航空职业技术学院|官网 (jlthedu.com)为目标 第一步&#xff1a;查看cdn和域名被注册的信息 可以查询域名信息的…

React(六)——Redux

文章目录 项目地址基本理解一、配置Redux store二、创建slice配置到store里并使用三、给Slice配置reducers&#xff0c;用来修改初始值 项目地址 教程作者&#xff1a;教程地址&#xff1a; 代码仓库地址&#xff1a; 所用到的框架和插件&#xff1a; dbt airflow基本理解 s…

如何利用ATECLOUD平台来实现数据报告的导出和数据分析?-纳米软件

1.数据报告导出 选择报告模板&#xff1a;ATECLOUD 平台通常会提供多种预设的数据报告模板&#xff0c;这些模板是根据不同的测试场景和需求设计的。例如&#xff0c;在电源模块测试中&#xff0c;有针对输出电压、电流、功率等基本参数的报告模板&#xff0c;也有包含纹波系数…

[ZJCTF 2019]NiZhuanSiWei

[ZJCTF 2019]NiZhuanSiWei 上面代码&#xff0c;使用get上传了三个参数&#xff0c;在text者用力恒等于&#xff0c;然后就输出&#xff0c;接着第二个参数中出现flag就输出not now&#xff0c;接着第三个参数是反序了一下输出。 ?textdata://text/plain,welcome to the zjct…

JSONCPP 数据解析与序列化

常用类接口 Json::Value 类 用于存储 JSON 数据的核心类。它支持将数据解析为对象、数组或基本类型&#xff08;如字符串、数值等&#xff09; 赋值操作符&#xff1a;Value& operator(Value other); 用于将一个 JSON 值赋给另一个 JSON 值 Json::Value value; value &…

40分钟学 Go 语言高并发:【实战】并发安全的配置管理器(功能扩展)

【实战】并发安全的配置管理器&#xff08;功能扩展&#xff09; 一、扩展思考 分布式配置中心 实现配置的集中管理支持多节点配置同步实现配置的版本一致性 配置加密 敏感配置的加密存储配置的安全传输访问权限控制 配置格式支持 支持YAML、TOML等多种格式配置格式自动…

ChatGPT 桌面版发布了,如何安装?

本章教程教大家如何进行安装。 一、下载安装包 官网地址地址&#xff1a;https://openai.com/chatgpt/desktop/ 支持Windows和MacOS操作系统 二、安装步骤 Windows用户下载之后&#xff0c;会有一个exe安装包&#xff0c;点击运行安装即可。 注意事项&#xff0c;如果Windows操…

【Electron学习笔记(二)】基于Electron开发应用程序

基于Electron开发本地应用程序 基于Electron开发本地应用程序前言正文1、创建 pages 目录2、创建 index.html 文件3 、创建 html.css 文件4 、main.js里引入页面5 、运行 start 命令6 、启用开发者模式7 、解决内容安全策略8、完善窗口行为9、配置自动重启&#xff0c;保存后自…

力扣--LCR 154.复杂链表的复制

题目 请实现 copyRandomList 函数&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个节点除了有一个 next 指针指向下一个节点&#xff0c;还有一个 random 指针指向链表中的任意节点或者 null。 提示&#xff1a; -10000 < Node.val < 10000 Node.random 为…

windows server 2019 启动 nginx 报错

环境 &#xff1a;windows server 2019 &#xff0c;nginx-1.19.7 背景&#xff1a; 自己经常用这个 nginx 包作为 web 服务器。今天发现 部署到 server 2019 上直接报错了。这可是原生的包&#xff0c;我啥也没改&#xff0c;怎么可能报错。而且之前在 其他服务器用都没问题…

在ASP.NET Core WebAPI 中使用轻量级的方式实现一个支持持久化的缓存组件

前言 在 WebAPI 开发中&#xff0c;缓存是一种常用的优化手段。Redis 是广泛使用的缓存解决方案&#xff0c;但在某些场景下&#xff0c;我们可能不希望引入第三方依赖&#xff0c;而是希望使用轻量级的方式实现一个支持持久化的缓存组件&#xff0c;满足以下需求&#xff1a;…