首页 > 编程语言 >C++笔记---可变参数模板

C++笔记---可变参数模板

时间:2024-10-31 18:46:35浏览次数:6  
标签:... emplace args back C++ --- 参数 模板

1. 简单介绍与基本语法

可变参数模板是指模板的类型参数列表的的参数个数可变

C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:

模板参数包:表示零或多个模板参数。

函数参数包:表示零或多个函数参数。

参数包的加入使得我们可以更加方便地设计出参数个数可变的函数,就比如模拟实现printf。

但C语言由于不支持模板,所以实现printf的方式十分复杂,查了我也没看明白。

声明该类型模板的语法

template<class ...Args> 
Type Func(Args... args) 
{}

template<class ...Args> 
Type Func(Args&... args) // 每个参数都按左值引用方式接收
{}

template<class ...Args> 
Type Func(Args&&... args) // 每个参数都按万能引用方式接收
{}

省略号用来指出一个由模板参数或函数参数构成的一个包:

在模板参数列表中,class...或typename...指出接下来的参数表示零或多个类型列表。

在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表。

函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。

可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。

"sizeof..."运算符

这里我们引入一个新的运算符 "sizeof..." ,用于计算参数包中参数的个数:

template <class ...Args>
void Print(Args&&... args)
{
    cout << sizeof...(args) << endl;
}

int main()
{
    double x = 2.2;
    Print(); // 包⾥有0个参数
    Print(1); // 包⾥有1个参数
    Print(1, string("xxxxx")); // 包⾥有2个参数
    Print(1.1, string("xxxxx"), x); // 包⾥有3个参数
    return 0;
}

2. 包扩展(参数包展开)

参数包并不能如我们所想地去直接访问其内容,我们只能采取如下两种方式来访问其包含的参数:

递归展开

虽然我们不能直接取得参数包中的参数,但是在用参数包进行传参时,参数包中的参数会自动匹配形参。

由此为启发,我们可以设计一个参数列表为一个参数和一个参数包的递归模板函数:

Type Func()
{
    // ...
}

template<class T, ...Args>
Type Func(T& x, Args... args)
{
    // ...
    Func(args...);
}

函数体中是对单个参数 "x" 进行操作的逻辑。

注意,不能将无参重载版本去掉而改成下面这样:

template<class T, ...Args>
Type Func(T& x, Args... args)
{
    // ...
    if(sizeof...(args) != 0)
        Func(args...);
}

假如按照这样的方式来写,无参的Func虽然不会被调用,但是在编译阶段,编译器会尝试实例化出无参的版本。

而我们给出的模板至少接收一个参数,编译器无法实例化出无参Func,会直接报错。 

通过这样的方式,我们可以将参数包中的参数从前向后一个一个地拆出来,分别操作。

例如:

void ShowList()
{
    // 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数
    cout << endl;
}
 
template <class T, class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";
    // args是N个参数的参数包
    // 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包
    ShowList(args...);
}
 
// 编译时递归推导解析参数
template <class ...Args>
void Print(Args... args)
{
    ShowList(args...);
}
 
int main()
{
    Print();
    Print(1);
    Print(1, string("xxxxx"));
    Print(1, string("xxxxx"), 2.2);
    return 0;
}

也可一次拆出多个参数,在递归函数中增加几个形参即可。但此时就需要多写几个重载函数来处理参数个数不够多的情况了。 

复合函数

template <class T>
const T& GetArg(const T& x)
{
    // ...
    // 返回值不一定是x,可根据需要确定
    return x;
}
 
template <class ...Args>
void Arguments(Args... args)
{}

template <class ...Args>
void test(Args... args)
{
    // 注意GetArg必须返回或者到的对象,这样才能组成参数包给Arguments
    Arguments(GetArg(args)...);
}

"Arguments(GetArg(args)...);" 的含义是,将参数包中的参数依次传入GetArg函数中进行调整之后,形成新的参数包传给Arguments函数。

这样的写法不仅可以用于展开参数包,还可以用于需要对参数包每个参数都进行处理之后进行函数调用的场景。

注意:Arguments函数的调用是必要的,括号内的 "GetArg(args)..." 并不能独立作为表达式而存在。

3. emplace系列接口

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

template <class... Args> 
iterator emplace (const_iterator position, Args&&... args);

C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,能够完成与push_back,insert等相同的功能,而参数包的存在使得emplace系列接口不止可以接受要插入的对象,还可以接受构造要插入对象的参数。

以前我们在进行插入时,要么用已有对象,要么用匿名对象,要么用隐式类型转换,总之在进入函数之前,对象就需要被构造好,进入函数之后再通过拷贝构造或移动构造,构造出形参进行插入。

而emplace系列接口的参数包可以直接接受构造对象的参数而不用发生隐式类型转换,在函数内部直接将参数包层层传递(如果复用了其他函数的话)给构造函数构造出对象,并进行插入。

template <class... Args>
void emplace_back(Args&&... args)
{
	insert(end(), std::forward<Args>(args)...);
}

template <class... Args>
iterator emplace(const_iterator pos, Args&&... args)
{
	Node* cur = pos._node;
	Node* newnode = new Node(std::forward<Args>(args)...);
	Node* prev = cur->_prev;
	// prev newnode cur
	prev->_next = newnode;
	newnode->_prev = prev;
	newnode->_next = cur;
	cur->_prev = newnode;
	return iterator(newnode);
}

也就是说emplace系列接口可以将函数外的构造转移到函数内进行构造,以此节省掉一次拷贝构造或移动构造。

emplace系列总体而言是更高效的,推荐以后使用emplace系列替代insert和push系列。

#include<list>
int main()
{
	list<string> lt;

	// 传左值,跟push_back一样,走拷贝构造
	string s1("111111111111");
	lt.emplace_back(s1);

	// 右值,跟push_back一样,走移动构造
	lt.emplace_back(move(s1));

	// 直接把构造string参数包往下传,直接用string参数包构造string
	// 这里达到的效果是push_back做不到的
	lt.emplace_back("111111111111");


	list<pair<string, int>> lt1;
	// 跟push_back一样
	// 构造pair + 拷贝/移动构造pair到list的节点中data上
	pair<string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	
	// 跟push_back一样
	lt1.emplace_back(move(kv));

	// 直接把构造pair参数包往下传,直接用pair参数包构造pair
	// 这⾥达到的效果是push_back做不到的
	lt1.emplace_back("苹果", 1);
	return 0;
}

标签:...,emplace,args,back,C++,---,参数,模板
From: https://blog.csdn.net/2302_80372340/article/details/143370636

相关文章

  • 常用算法模板
    数论组合数方法1(小数据)数据范围\(1\leqn\leq10000\),\(1\leqb\leqa\leq2000\)说明通过递推预处理组合数公式\(C^{b}_{a}=C^{b}_{a-1}+C^{b-1}_{a-1}\)LLC[N][N];voidinit(){for(inti=0;i<N;i++){for(intj=0;j<=......
  • Kafka社区KIP-500中文译文(去除ZooKeeper)
    原文链接:https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum译者:关于Kafka3.x版本最大的一个变化即是解除了对ZooKeeper的依赖,而本文的作者是大神Colin,他高屋建瓴地阐述去ZK的整个过程,更多的是偏整体设计,......
  • 《阅读笔记 - 第三部分》
    第三部分(第6-8章)主要围绕代码结构与设计原则展开,收获颇丰。第6章“当你编码时”提到在编码过程中要保持代码清晰、简洁且合理使用注释。清晰的代码结构便于自己和他人后续维护和扩展,注释则是代码的“说明书”,能让阅读者快速理解代码意图。第7章“重复的危害”深刻......
  • 《阅读笔记 - 第二部分》
    读完《程序员修炼之道》第二部分(第3-5章),对保障代码质量有了更深的认识。第3章“基本工具”让我意识到选择和熟练使用合适工具的重要性。文本编辑器、版本控制系统等,它们就像程序员的武器,只有精通其用法,才能在编程战场上高效作战。不同工具适用于不同场景,我们要根据项目需......
  • 《阅读笔记 - 第一部分》
    在阅读《程序员修炼之道——从小工到专家》的开篇部分(第1-2章)后,深感这是为程序员奠定正确思维基石的重要内容。第1章“注重实效的哲学”让我明确了作为程序员应有的责任感。“关心你的技艺”这句提醒,让我意识到编程不仅是一份工作,更是一门需要用心雕琢的艺术。我们要......
  • 《链表篇》---两两交换链表中的节点(中等)
    题目传送门1.定义一个虚拟节点链接链表2.定义一个当前节点指向虚拟节点3.在当前节点的下一个节点和下下一个节点都不为null的情况下。定义node1和node2。保存当前节点后面两个节点的地址。cur.next=node2;node1.next=node2.next;node2.next=node1;cur=node1;4.......
  • 《链表篇》---删除链表的倒数第N个节点(中等)
    题目传送门 方法一:计算链表长度(迭代)1.计算链表长度,并且定义哑节点链接链表。2.从哑节点开始前进length-n次。即为被删除节点的前置节点。3.进行删除操作。4.返回哑节点的后置节点classSolution{publicListNoderemoveNthFromEnd(ListNodehead,intn){......
  • 基础知识-7-选择结构【if switch】
    概念说明1.if与switch的作用与区别if语句用于根据一个条件表达式的结果来决定是否执行某个代码块。它可以处理各种类型的条件,包括范围判断和复杂的逻辑表达式。intnum=5;if(num>3){cout<<"num大于3"<<endl;}switch语句用于......
  • Ethernet 系列(6)-- 基础学习::OSI Model
    (写在前面:最近在学习车载以太网的知识,顺便记录一下知识点。)OSI(OpenSystemInterconnect)模型是一种网络通信框架,由国际标准化组织(‌ISO)在1985年提出,旨在为不同制造商和技术提供商的网络设备和软件提供一个通用的兼容和通信标准。这个模型将复杂的网络通信过程分解为七个独......
  • 《使用Gin框架构建分布式应用》阅读笔记:p272-p306
    《用Gin框架构建分布式应用》学习第15天,p272-p306总结,总35页。一、技术总结1.TDD(test-drivendevelopment)虽然经常看到TDD这个属于,从本人的工作经历看,实际开发中用得相对较少。2.unitest(单元测试)go语言开发中,使用testify进行单元测试开发。(1)创建测试文件测试文件以xx......