【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.mzph.cn/news/887359.shtml

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

相关文章

TCP为什么需要三次握手?两次握手或四次握手可以吗?

&#xff08;1&#xff09;三次握手可以保证双方具有接收和发送的能力 第一次握手服务端可以确认客户端的发送能力和服务端的接收能力是正常的&#xff1b;第二次握手客户端可以确认客户端和服务端的收发能力是正常的&#xff0c;但是服务端无法确认客户端的接收能力是正常的&…

nature communications论文 解读

题目《Transfer learning with graph neural networks for improved molecular property prediction in the multi-fidelity setting》 这篇文章主要讨论了如何在多保真数据环境&#xff08;multi-fidelity setting&#xff09;下&#xff0c;利用图神经网络&#xff08;GNNs&…

Cmakelist.txt之Linux-redis配置

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(redis_linux_test LANGUAGES C) ​ ​ ​ add_executable(redis_linux_test main.c) ​ # 设置hiredis库的头文件路径和库文件路径 set(Hiredis_INCLUDE_DIR /usr/local/include/hiredis) set(Hiredis_LIBRA…

基于Qt/C++/Opencv实现的一个视频中二维码解析软件

本文详细讲解了如何利用 Qt 和 OpenCV 实现一个可从视频和图片中检测二维码的软件。代码实现了视频解码、多线程处理和界面更新等功能&#xff0c;是一个典型的跨线程图像处理项目。以下分模块对代码进行解析。 一、项目的整体结构 项目分为以下几部分&#xff1a; 主窗口 (M…

C语言练习.if.else语句.strstr

今天在做题之前&#xff0c;先介绍一下&#xff0c;新学到的库函数strstr 想要使用它&#xff0c;要先给它一个头文件<string.h> char *strstr(const char*str1,const char*str2); 首先&#xff1a;1.strstr的返回值是char&#xff0c;字符类型的。 2.两个实参&#xff…

丹摩|丹摩智算平台深度评测

1. 丹摩智算平台介绍 随着人工智能和大数据技术的快速发展&#xff0c;越来越多的智能计算平台涌现&#xff0c;为科研工作者和开发者提供高性能计算资源。丹摩智算平台作为其中的一员&#xff0c;定位于智能计算服务的提供者&#xff0c;支持从数据处理到模型训练的全流程操作…

美创科技入选2024数字政府解决方案提供商TOP100!

11月19日&#xff0c;国内专业咨询机构DBC德本咨询发布“2024数字政府解决方案提供商TOP100”榜单。美创科技凭借在政府数据安全领域多年的项目经验、技术优势与创新能力&#xff0c;入选收录。 作为专业数据安全产品与服务提供商&#xff0c;美创科技一直致力于为政府、金融、…

CSS —— 子绝父相

相对定位&#xff1a;占位&#xff1b;不脱标 绝对定位&#xff1a;不占位&#xff1b;脱标 希望子元素相对于父元素定位&#xff0c;又不希望父元素脱标&#xff08;父元素占位&#xff09; 子级是 绝对定位&#xff0c;不会占有位置&#xff0c; 可以放到父盒子里面的任何一…

废品买卖回收管理系统|Java|SSM|Vue| 前后端分离

【重要①】前后端源码万字文档部署文档 【重要②】正版源码有问题包售后 【包含内容】 【一】项目提供非常完整的源码注释 【二】相关技术栈文档 【三】源码讲解视频 【其它服务】 【一】可以提供远程部署安装&#xff0c;包扩环境 【…

Flink学习连载第二篇-使用flink编写WordCount(多种情况演示)

使用Flink编写代码&#xff0c;步骤非常固定&#xff0c;大概分为以下几步&#xff0c;只要牢牢抓住步骤&#xff0c;基本轻松拿下&#xff1a; 1. env-准备环境 2. source-加载数据 3. transformation-数据处理转换 4. sink-数据输出 5. execute-执行 DataStream API开发 //n…

构建高效在线教育:SpringBoot课程管理系统

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理在线课程管理系统的相关信息成为必然。开发…

【Redis_Day6】Hash类型

【Redis_Day6】Hash类型 Hash类型操作hash的命令hset&#xff1a;设置hash中指定的字段&#xff08;field&#xff09;的值&#xff08;value&#xff09;hsetnx&#xff1a;想hash中添加字段并设置值hget&#xff1a;获取hash中指定字段的值hexists&#xff1a;判断hash中是否…

在SQLyog中导入和导出数据库

导入 假如我要导入一个xxx.sql&#xff0c;我就先创建一个叫做xxx的数据库。 然后右键点击导入、执行SQL脚本 选择要导入的数据库文件的位置&#xff0c;点击执行即可 注意&#xff1a; 导入之后记得刷新一下导出 选择你要导出的数据库 右键选择&#xff1a;备份/导出、…

详细教程-Linux上安装单机版的Hadoop

1、上传Hadoop安装包至linux并解压 tar -zxvf hadoop-2.6.0-cdh5.15.2.tar.gz 安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1u59OLTJctKmm9YVWr_F-Cg 提取码&#xff1a;0pfj 2、配置免密码登录 生成秘钥&#xff1a; ssh-keygen -t rsa -P 将秘钥写入认…

实时数据开发 | 怎么通俗理解Flink容错机制,提到的checkpoint、barrier、Savepoint、sink都是什么

今天学Flink的关键技术–容错机制&#xff0c;用一些通俗的比喻来讲这个复杂的过程。参考自《离线和实时大数据开发实战》 需要先回顾昨天发的Flink关键概念 检查点&#xff08;checkpoint&#xff09; Flink容错机制的核心是分布式数据流和状态的快照&#xff0c;从而当分布…

鸿蒙网络编程系列50-仓颉版TCP回声服务器示例

1. TCP服务端简介 TCP服务端是基于TCP协议构建的一种网络服务模式&#xff0c;它为HTTP&#xff08;超文本传输协议&#xff09;、SMTP&#xff08;简单邮件传输协议&#xff09;等高层协议的应用程序提供了可靠的底层支持。在TCP服务端中&#xff0c;服务器启动后会监听一个或…

DataGrip 连接 Redis、TongRDS

连接 Redis 或 TongRDS 有些旧版本 没有 redis 驱动用不了 1&#xff09;选择驱动 2&#xff09;添加连接信息 3&#xff09;测试连接 4&#xff09;保存连接 5&#xff09;使用案例

DevExpress控件 基本使用

DevExpress控件 一、DevExpress简介 1、所有编辑器的公共功能 全部都可以绑定数据&#xff1b; 全部都可以独立使用或用于由 Developer Express 提供的容器控件 (XtraGrid、XtraVerticalGrid、XtraTreeList 和 XtraBars) 内的内置编辑&#xff1b; 全部都使用相同的样式、外…

mybatis学习(一)

声明&#xff1a;该内容来源于动力节点&#xff0c;本人在学习mybatis过程中参考该内容&#xff0c;并自己做了部分笔记&#xff0c;但个人觉得本人做的笔记不如动力节点做的好&#xff0c;故使用动力节点的笔记作为后续mybatis的复习。 一、MyBatis概述 1.1 框架 在文献中看…

C0034.在Ubuntu中安装的Qt路径

Qt安装路径查询 在终端输入qmake -v如上中/usr/lib/x86_64-linux-gnu就是Qt的安装目录&#xff1b;