【C++11 ——— 可变参数模板】

ops/2024/9/24 17:13:52/

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

  • 可变参数模板的概念
  • 可变参数模板的定义方式
  • 参数包的展开
    • 递归式展开参数包
    • 逗号表达式展开参数包
  • emplace
    • emplace 的使用
    • emplace 的优势

可变参数模板的概念

C++11之前,函数模板和类模板中的模板参数数量固定的。可变参数模板打破了这个限制,提供了一种编写泛型代码的方法,让我们可以定义接受可变数量参数的模板。这极大地增加了模板的灵活性和表达能力。

可变参数模板的定义方式

函数的可变参数模板定义方式如下:

//Args是一个模板参数包
//args是一个函数参数包
//声明一个参数包Args... args,这个参数包中可以包含0到任意数量个模板参数。
template <class ... Args>
void ShowList(Args... args)
{}
  • 模板参数包:template<class ... Args> 中的Args... 表示一个模板的参数包,它可以包含0个或多个模板参数。
  • 函数参数包:void ShowList(Args... args)中的args... 表示一个函数的参数包,允许函数接受任意数量的参数。

...在参数包中的作用


  • ... 在模板参数包中的作用: 用于声明一个可以包含多个参数的包,一般位于参数名称之前。
  • ... 在函数参数包中的作用: 用于表示这个函数可以接受多个参数,一般位于参数名称之后。

当定义了一个可变参数模板之后,就可以像普通函数一样调用它:

//Args是一个模板参数包
//args是一个函数参数包
//声明一个参数包Args... args,这个参数包中可以包含0到任意数量个模板参数。
template <class ... Args>
void ShowList(Args... args)
{}int main()
{ShowList(1, 2, 3); // 可以传入任意数量的参数ShowList();       // 也可以传入0个参数ShowList(1, 2, 3, "string");//也可以传入不同类型的参数return 0;
}

也可以通过sizeof来打印参数的个数:
在这里插入图片描述

为什么这里的调用时sizeof...(args)?


这里的sizeof...是一个操作符,用于计算参数包中元素的数量,其必须与参数包一起使用,而args是一个函数参数包的名称,表示在函数调用的时候传入的参数。
sizeof...是一个变体,专门用以计算参数包的大小,它只能与参数包一起使用。这个是C++11的规定。


原理:

  • 当你调用ShowList(1,2,3)的时候,args被推导为{1,2,3},这是一个包含三个参数的包。
  • sizeof...(args)计算的是args中的元素数量,结果为3
    在编译期间,编译器会根据传入的参数数量和类型来确定args的具体内容,并计算出数量。

但是我们仍不能方便的随机获取参数,同时可变参数的语法也不支持args[i],所以如何获取就值得我们探讨一番!

为什么可变参数模板的语法不支持args[i]呢?


  1. 可变参数模板是编译时编程
    可变参数模板是在编译期间展开的,编译器需要根据参数包的内容生成对应的代码。在编译期间,参数包中的参数是未知的,因此无法确定具体的下标。
  2. 参数包是一个整体
    在可变参数模板中,参数包args...是一个整体传递的,它代表了0个或者多个参数,无法像数组那样通过下标访问单个参数。
  3. 参数包需要展开
    要访问参数包中的单个参数,需要对参数包进行展开(后续会谈论)。常见的展开方式由递归展开、逗号表达式、初始化列表展开等。这些展开方式都是基于对参数包的遍历,而不是通过下标访问。
  4. 参数包的长度是编译常量
    通过sizeof...(args)可以在编译期间获取参数包长度。但这个长度是编译期常量,无法在运行时改变。因此,参数包的长度是固定的,无法像数组一样动态改变。

参数包的展开

递归式展开参数包

 递归展开参数包实际上是通过逐步剥离参数包中的元素来实现的。具体来说,对于下面的代码,编译器在编译的时候会根据传入的实参推导出模板参数的类型,并且生成相应的函数调用。每次递归调用都会减少参数包的大小,直到仅剩一个为止。

template<class T>
void ShowList(T value)
{cout << value << endl;
}template<class T,class ...Args>
void ShowList(T value, Args... args)
{cout << value << " ";ShowList(args...);
}int main()
{ShowList(1, 2, 3);
}

在这里插入图片描述
调用情况示例
当我们调用 ShowList(1, 2, 3) 时,调用栈的变化过程如下:

// 第一次调用
ShowList<int, int, int>(1, 2, 3);// 打印 1ShowList<int, int>(2, 3);// 打印 2ShowList<int>(3);// 打印 3// 递归终止

其次也可以这样写,写一个不带参的递归终止函数,当传入的args为空的时候,就会直接匹配第一个无模板无参的函数。
在这里插入图片描述

逗号表达式展开参数包

逗号表达式可以用来展开参数包,它的基本思路如下:

  1. 将逗号表达式的最后一个表达式设置为一个整型值,确保逗号表达式返回的是一个整型值。
  2. 将处理参数包中参数的动作封装成一个函数,将该函数的调用作为逗号表达式的第一个表达式。
  3. 在列表初始化时使用逗号表达式展开参数包。

这样一来,在执行逗号表达式时就会先调用处理函数处理对应的参数,然后再将逗号表达式中的最后一个整型值作为返回值来初始化整型数组。

下面是一个代码示例:

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, 2, 3);cout << "---------------" << endl;ShowList("sad", 1);return 0;
}

在这里插入图片描述

通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

那么为什么要返回一个 0 呢?


这是因为在列表初始化数组时,每个元素的表达式都需要返回一个值。如果不返回任何值,编译器会报错。所以,我们选择返回一个 0 作为占位符,因为这个值最终不会被使用到。关键是,逗号表达式会先执行左边的表达式,也就是打印参数,然后返回右边的值。
展开后,{ (PrintArg(arg1), 0), (PrintArg(arg2), 0), ... } 这个列表初始化会被展开成多个逗号表达式,每个表达式都会打印出对应的参数,并返回一个 0。这些 0 值会被用于初始化数组 arr
总之, 在使用逗号表达式展开参数包时,返回一个占位值是为了满足列表初始化的语法要求,而真正的目的是在逗号表达式的左边执行一些操作,比如打印参数。

emplace

emplace 的使用

emplace 是 C++11 中引入的一组成员函数,主要用于 STL 容器(如 vectorlistdeque 等),允许在容器中直接构造元素,而不是先创建对象再将其拷贝到容器中。

  1. 基本用法
    emplace 系列函数包括 emplace_backemplace_frontemplace,它们的基本用法如下:
  • emplace_back: 在容器的末尾构造一个新元素。
  • emplace_front: 在容器的开头构造一个新元素。
  • emplace: 在指定位置之前构造一个新元素。

示例代码:

#include <iostream>
#include <vector>
#include <string>class Person {
public:Person(int age, const std::string& name) : _age(age), _name(name) {std::cout << "Constructing: " << name << std::endl;}int _age;std::string _name;
};int main() {std::vector<Person> people;// 使用 emplace_back 直接构造对象people.emplace_back(25, "Alice");people.emplace_back(30, "Bob");// 使用 emplace 在指定位置构造对象people.emplace(people.begin() + 1, 22, "Charlie"); // 在 Bob 前面插入 Charliefor (const auto& person : people) {std::cout << person.name << " is " << person.age << " years old." << std::endl;}return 0;
}

在这里插入图片描述

emplace 的优势

效率更高的原因

  1. 直接构造:
    使用 emplace 系列函数时,传递的参数会直接用于构造对象,而不是先创建一个临时对象再将其拷贝到容器中。这减少了不必要的拷贝或移动构造。
  2. 避免临时对象:
    当使用 push_backinsert 时,通常需要创建一个临时对象,然后将其拷贝到容器中。这会导致额外的构造和析构开销。而 emplace 直接在容器内部构造对象,避免了这个过程。
  3. 支持参数包:
    emplace 支持可变参数模板,可以接受任意数量和类型的参数,这使得它在构造复杂对象时更加灵活。例如,可以直接传递多个参数来构造一个对象,而不需要先创建一个临时对象。

何时不优于传统插入

  • 如果传入的是左值对象或右值对象,emplace 的效率可能与传统的 push_backinsert 相当,因为在这些情况下,仍然可能会调用拷贝构造函数或移动构造函数。
  • emplace 的优势主要体现在构造新对象时,尤其是当传入参数包时,可以避免创建临时对象和拷贝构造。

http://www.ppmy.cn/ops/110703.html

相关文章

JDK8的一些主要的新特性

JDK8&#xff08;Java Development Kit 8&#xff09; 是一个重要的版本&#xff0c;带来了许多显著的特性和改进&#xff0c;极大地提升了 Java 语言的功能性和开发效率。以下是 JDK 8 的一些主要新特性&#xff1a; 一、Lambda 表达式 1.简化匿名内部类的写法&#xff0c;…

95、k8s之rancher可视化

一、ranker 图形化界面 图形化界面进行k8s集群的管理 rancher自带监控----普罗米修斯 [rootmaster01 opt]# docker load -i rancher.tar ##所有节点 [rootmaster01 opt]# docker pull rancher/rancher:v2.5.7 ##主节点[rootmaster01 opt]# vim /etc/docker/daemon.jso…

ubuntu升级python版本

ubuntu升级python版本 # 更新包列表 sudo apt update# 安装 Python 所需的软件包 sudo apt install software-properties-common# 添加 Python PPA(以 Python 3.12 为例) sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update# 安装新版本 sudo apt install python3.12…

浙大数据结构:04-树6 Complete Binary Search Tree

这道题利用了完全二叉树的性质&#xff0c;我也参考了一些代码写的。 &#xff08;自己一开始写了别的方法&#xff0c;但一直过不了最后一个测试点&#xff0c;红温了&#xff09; 机翻&#xff1a; 1、条件准备 用vector存输入的数据&#xff0c;另一个数组存输出的结果&a…

免费线上研讨会 | Ansys Speos 生医光学解决方案-内窥镜设计与仿真

随着光学设计的不断进步&#xff0c;内窥镜的仿真分析变得越来越重要。Speos软件作为光学仿真领域的佼佼者&#xff0c;能够提供高精度的照明和视觉仿真&#xff0c;帮助设计师评估内窥镜在不同条件下的性能&#xff0c;优化照明设计&#xff0c;提高图像质量&#xff0c;从而提…

拖放WORD文件朗读全文

把WORD拖放到tkinter的窗口&#xff0c;就可以朗读整改word文件的内容。 代码&#xff1a; # -*- coding: utf-8 -*- """ Created on Tue Sep 10 17:09:35 2024author: YBK """ import pyttsx3 import comtypes.client import os import tkint…

初识c++:入门基础

打字不易&#xff0c;留个赞再走吧~~ 目录 一.第一个c程序二.命名空间 namespace三.C输⼊&输出四.缺省参数 C兼容C语⾔绝⼤多数的语法&#xff0c;所以C语⾔实现的hello world依旧可以运⾏&#xff0c;C中需要把定义⽂件 代码后缀改为.cpp 一.第一个c程序 做好准备我们来写…

干货 | Selenium+chrome自动批量下载地理空间数据云影像

1.背景介绍 1.1地理空间数据云 由中国科学院计算机网络信息中心科学数据中心成立的地理空间数据云平台是常见的下载空间数据的平台之一。其提供了较为完善的公开数据&#xff0c;如LANDSAT系列数据&#xff0c;MODIS的标准产品及其合成产品&#xff0c;DEM数据&#xff08;SR…