【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++11
    HeapOnly(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++语言旅途中有所帮助!

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

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

相关文章

js+jquery实现经典推箱子游戏

纯前端项目&#xff0c;只使用html,css,js,jquery实现经典推箱子游戏&#xff0c;直接下载本地双击index.html即可运行体验。 游戏展示 开始界面 完成游戏 代码展示

【Pytest+Yaml+Allure】实现接口自动化测试框架

一、框架思想 requestsyamlpytestallure实现接口自动化框架。结合数据驱动和分层思想&#xff0c;将代码与数据分离&#xff0c;易维护&#xff0c;易上手。使用yaml编写编写测试用例&#xff0c;利用requests库发送请求&#xff0c;使用pytest管理用例&#xff0c;allure生成…

[HarmonyOS] 解决HMRouter路由地址无法抽取的问题

解决HMRouter路由地址无法抽取的问题 背景 最近开始学习HarmonyOS开发&#xff0c;搭建项目的时候采用了 HMRouter 路由框架&#xff0c;在项目里使用到路由跳转&#xff0c;官方链接在这&#xff1a; https://gitee.com/hadss/hmrouter/blob/master/HMRouterLibrary/README…

ElasticSearch学习了解笔记

搜索引擎的原理&#xff1a; 1、查询分析&#xff08;自然语言处理&#xff09;理解用户需求 2、分词技术 3、关键词搜索匹配 4、搜索排序 lucence Lucene 是一个成熟的权威检索库 Elasticsearch 的搜索原理简单过程是&#xff0c;索引系统通过扫描文章中的每一个词&#xff…

ffmpeg视频滤镜:提取缩略图-framestep

滤镜描述 官网地址 > FFmpeg Filters Documentation 这个滤镜会间隔N帧抽取一帧图片&#xff0c;因此这个可以用于设置视频的缩略图。总体上这个滤镜比较简单。 滤镜使用 滤镜参数 framestep AVOptions:step <int> ..FV....... set frame st…

【C++11】可变参数模板/新的类功能/lambda/包装器--C++

文章目录 一、可变参数模板1、基本语法及原理2、包扩展3、empalce系列接口 二、新的类功能1、默认的移动构造和移动赋值2、成员变量声明时给缺省值3、defult和delete4、final与override 三、STL中一些变化四、lambda1、lambda表达式语法2、捕捉列表3、lambda的应用4、lambda的原…

云网络基础- TCP/IP 协议

文章目录 典型服务模式TCP/IP 协议设置和查看IPIP地址的分类:IP地址组成: 网络位主机位组成克隆:产生一台新的虚拟机win2008 典型服务模式 • C/S,Client/Server架构 – 由服务器提供资源或某种功能 – 客户机使用资源或功能 TCP/IP 协议 • TCP/IP是最广泛支持的通信协议集合…

java基础知识(Math类)

引入&#xff1a;Math 类包含用于执行基本数学运算的方法&#xff0c;如初等指数、对数、平方根 import java.util.Math 1.abs绝对值 int abs Math.abs(-9); 2.pow求幂 double pow Math.pow(2,4); 3.向上取整 double ceil Math.ceil(3.9);//ceil 4 4.向下取整 dou…

什么是 WPF 中的依赖属性?有什么作用?

依赖属性&#xff08;Dependency Property&#xff09;是 WPF 的一个核心概念&#xff0c;它为传统的 .NET 属性提供了增强功能&#xff0c;支持绑定、样式、动画和默认值等功能。通过依赖属性&#xff0c;WPF 提供了一种灵活的数据驱动的方式来处理 UI 属性。 1. 什么是依赖属…

线性代数空间理解

学习线性代数已经很久&#xff0c;但是在使用过程中仍然还是不明所以&#xff0c;比如不知道特征向量和特征值的含义、矩阵的相乘是什么意思、如何理解矩阵的秩……。随着遇到的次数越来越多&#xff0c;因此我决定需要对线性代数的本质做一次深刻的探讨了。 本次主要是参考了3…

Jmeter的组件执行顺序

在 Apache JMeter 中&#xff0c;组件的加载和执行顺序遵循一定的规则&#xff0c;但有些组件在同一层级中可能会根据它们在测试计划中的位置来决定具体的执行顺序。以下是这些组件的大致加载和执行顺序&#xff0c;以及哪些组件属于同一层级&#xff1a; 线程组&#xff08;Th…

计算机网络八股整理(一)

计算机网络八股文整理 一&#xff1a;网络模型 1&#xff1a;网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型&#xff0c;它由七层组成&#xff0c;从上到下分别是&#xff1a;应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;…

今天你学C++了吗?——C++中的类与对象(第二集)

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…

【C++习题】14.滑动窗口_找到字符串中所有字母异位词

文章目录 题目链接&#xff1a;题目描述&#xff1a;解法C 算法代码&#xff1a;图解 题目链接&#xff1a; 438. 找到字符串中所有字母异位词 题目描述&#xff1a; 解法 暴力解法&#xff1a; 字母排序后运用滑动窗口解题。 滑动窗口哈希表&#xff1a; 我们可以优化一下&am…

Spring Boot集成MyBatis-Plus:自定义拦截器实现动态表名切换

Spring Boot集成MyBatis-Plus&#xff1a;自定义拦截器实现动态表名切换 一、引言 介绍动态表名的场景需求&#xff0c;比如多租户系统、分表分库&#xff0c;或者不同业务模块共用一套代码但操作不同表。说明 MyBatis-Plus 默认绑定固定表名的问题。 二、项目配置 1. 集成 M…

深入探索API爬虫工作的技术难点与高效解决思路

在大数据与信息化高速发展的今天&#xff0c;API&#xff08;应用程序编程接口&#xff09;爬虫成为了数据收集与分析的重要工具。然而&#xff0c;API爬虫工作并非一帆风顺&#xff0c;它面临着诸多技术挑战。本文将深入探讨几个API爬虫工作的技术难点&#xff0c;并提出相应的…

css效果

css炫彩流光圆环效果 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title><style>*{margin: 0;padding: 0;}body{width: 100%;height: 100vh;}.container{position: relative;width: 100%;height: 100vh…

arm Rk1126 编译Qt工程报错: Could not find qmake spec

首先修改qmake.conf文件&#xff0c;配置好正确的交叉编译工具&#xff1a; 然后执行编译&#xff1a; /opt/Rv1126/Rv1126-盒子代码/rv1126-qt5-sdk/bin/qmake untitled.pro 报错。 原因&#xff1a;中文路径。修改路径为英文路径即可

zabbix监控进程

使用zabbix监控指定的进程&#xff0c;现在主要使用监控一些用java python写的一些微服务模块&#xff0c;我这边用于演示就直接使用nginx服务来演示了 创建监控项 name - 进程名称&#xff08;默认为 ALL PROCESSES);user - 用户名&#xff08;默认为 all users);state - 可能…

php 导出excel 一个单元格 多张图片

public function dumpData(){error_reporting(0); // 禁止错误信息输出ini_set(display_errors, 0); // 不显示错误$limit $this->request->post(limit, 20, intval);$offset $this->request->post(offset, 0, intval);$page floor($offset / $limit) 1 ;$wh…