C++11可变参数模板
·

文章目录
1.可变参数模板基础语法
1.1.核心概念定义
- 可变参数模板:C++11中支持的可变参数模板,实际上就是支持可变数量的参数的函数模板或类模板
形如:
//1.
template<class ...Args>
void Func(Arg... args){}
//2.
template<class ...Args>
void Func(Arg&... args) {}
//3.
template<class ...Args>
void Func(Arg&&... args) {}
- 参数包:就是可变参数模板中可变数目的参数
其中,参数包又分为两种:
模板参数包:表示零或多个模板参数
函数参数包:表示零或多个函数参数
- 省略号(…):在模板或函数参数中,我们用省略号来说明这个参数是一个包
在模板参数列表中,class…或typename…后面出现的参数就是参数包,表示零或多个类型列表
在函数参数列表中,类型名后面跟…指出接下来表示零或多个形参对象列表
函数参数包可以用左值引用,也可以用右值引用表示,实例化时遵循引用折叠原则
1.2.基本语法格式
- 在函数模板中的声明方式:
template <class... Args> 返回类型 函数名(Args&&... args) - 在类模板中的声明方式:
template <class... Args> class 类名 {} - 参数包大小的获取:可以通过
sizeof...(参数包名称)来获取参数包大小 - 空参数包支持:实例化时参数包不传值是合法的
#include <iostream>
#include <string>
// 1. 函数模板:按指定声明方式实现,支持空参数包
template <class... Args>
void FuncTemplate(Args&&... args) {
// 仅验证参数包特性:打印参数个数(空包时为0)
std::cout << "函数模板 - 参数个数:" << sizeof...(args) << std::endl;
}
// 2. 类模板:按指定声明方式实现,支持空参数包
template <class... Args>
class ClassTemplate {
public:
// 构造函数接收参数包,验证类模板的空包与多包支持
ClassTemplate(Args&&... args) {
std::cout << "类模板 - 构造函数参数个数:" << sizeof...(args) << std::endl;
}
};
int main() {
// 3. 空参数包验证:实例化/调用时不传实参(合法)
std::cout << "=== 空参数包测试 ===" << std::endl;
FuncTemplate(); // 函数模板空包调用
ClassTemplate<> classEmpty; // 类模板空包实例化(无构造实参)
// 补充:多参数包调用(验证非空场景与空包对比)
std::cout << "\n=== 多参数包测试(对比用) ===" << std::endl;
FuncTemplate(10, "hello", 3.14); // 函数模板多参数调用
ClassTemplate<int, std::string> classMulti(20, "world"); // 类模板多参数实例化
return 0;
}
运行结果:

2.可变参数模板核心原理
2.1.实例化时的编译器自动优化
在实例化可变参数模板时,编译器会根据传入实参的类型和数目,自动生成对应版本的函数或类,不用手动写重载
#include <iostream>
using namespace std;
template <class... Args>
void PrintArgCount(Args&&... args)//万能引用
{
cout << "参数个数:" << sizeof...(args) << endl;
}
int main()
{
PrintArgCount(10); // 调用1:1个int实参
PrintArgCount(20, "hello"); // 调用2:int + const char*实参
PrintArgCount(3.14, 'a', true); // 调用3:double + char + bool实参
return 0;
}
2.2.包扩展
- 由于参数包是一个不能直接访问的列表,其中包含了若干参数,所以我们必须把它拆成一个一个的单参数来访问,我们把这个过程称为
“包扩展”。 - nm其中最常用的扩展方式为递归扩展:通过递归函数每次提取参数包中的第一个参数,剩余参数重复执行递归函数,直到参数包为空。
#include <iostream>
#include <string>
using namespace std;
// 1. 递归终止条件:参数包为空时调用(无参数版本)
void PrintArgs()
{
cout << endl; // 所有参数打印完,换行
}
// 2. 递归版本:提取第一个参数,扩展剩余参数
template <class T, class... Args>
void PrintArgs(T first, Args&&... rest)
{
// 打印第一个参数
cout << first << " ";
// 递归调用:扩展剩余参数(rest... 拆分为单个参数)
PrintArgs(forward<Args>(rest)...);
}
int main()
{
PrintArgs(); // 空参数包:仅换行
PrintArgs(10, 20, 30); // 打印:10 20 30
PrintArgs("C++11", "可变参数模板", 3.14); // 打印:C++11 可变参数模板 3.14
return 0;
}
注意:
1.必须定义无参版本的函数,否则参数包为空时没有函数可以匹配,编译报错
2.完美转发forward<Args>(rest)...用来保留剩余参数的左右值属性,避免调用递归函数传参时剩余参数rest…属性失效(变量表达式属性是左值)
3.可变参数模板的核心应用
3.1.STL容器中的emplace_back接口:
在C++11之前,容器中的
push_back每次调用都需要通过临时对象来拷贝/移动构造,而C++11中新增的emplace_back接口,可以通过可变参数模板直接在容器中构造对象,避免了中间过程,大大提高了效率
模拟实现list中的emplace_back:
#include <iostream>
#include <utility>
#include <string>
using namespace std;
// 模拟 list 的节点类
template <class T>
struct ListNode
{
T _data;
ListNode* _next;
ListNode* _prev;
// 节点构造函数:支持可变参数(直接构造 T 对象)
template <class... Args>
ListNode(Args&&... args)
: _data(forward<Args>(args)...) // 用参数包构造 T 对象
, _next(nullptr)
, _prev(nullptr)
{
cout << "ListNode 构造:直接构造 T 对象" << endl;
}
};
// 模拟 list 类
template <class T>
class List
{
private:
ListNode<T>* _head; // 哨兵节点
public:
List()
{
// 初始化哨兵节点
_head = new ListNode<T>(); // 空参数包:调用 T 的默认构造
_head->_next = _head;
_head->_prev = _head;
}
// emplace_back:接收 T 构造所需的可变参数
template <class... Args>
void emplace_back(Args&&... args)
{
// 1. 创建新节点:传递参数包,直接构造 T 对象
ListNode<T>* newNode = new ListNode<T>(forward<Args>(args)...);
// 2. 将新节点插入链表尾部(简化逻辑)
ListNode<T>* tail = _head->_prev;
tail->_next = newNode;
newNode->_prev = tail;
newNode->_next = _head;
_head->_prev = newNode;
}
~List()
{
// 析构逻辑(简化)
ListNode<T>* cur = _head->_next;
while (cur != _head)
{
ListNode<T>* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
}
};
// 测试用的自定义类
class Person
{
public:
string _name;
int _age;
// 默认构造函数
Person() : _name(""), _age(0) {
cout << "Person 默认构造" << endl;
}
// 有参构造函数
Person(string name, int age)
: _name(name)
, _age(age)
{
cout << "Person 有参构造:" << _name << " " << _age << endl;
}
};
int main()
{
List<Person> lst;
// emplace_back:直接传递 Person 的构造参数,无临时对象
lst.emplace_back("张三", 20);
return 0;
}
运行结果:

3.2.封装通用的“参数转发”函数
可变参数模板结合完美转发,可以实现通用的“参数转发”函数,这样能避免传参实例化时,左右值属性的丢失
#include <iostream>
#include <utility>
#include <string>
using namespace std;
// 底层目标函数:打印参数
void TargetFunc(int a, const string& b, double&& c)
{
cout << "TargetFunc:" << a << " " << b << " " << c << endl;
}
// 通用转发函数:接收可变参数,转发给 TargetFunc
template <class... Args>
void ForwardFunc(Args&&... args)
{
// 完美转发:保留参数的左/右值属性
TargetFunc(forward<Args>(args)...);
}
int main()
{
int a = 10;
string b = "hello";
// 调用转发函数:a是左值,b是左值,1.23是右值
ForwardFunc(a, b, 1.23);
return 0;
}
运行结果:

4.注意事项
- 空参数包的显式实例化:类模板空参数包必须显式写为
ClassName<>,不能省略<>,函数模板的空参数包可以直接调用(Func()) - 包扩展必须有模式:不能直接写
args...,否则会编译报错,必须配合具体操作,如:Print(args)...,forward<Args>(arg)...等 - 递归终止条件不能少:使用递归扩展参数包时,必须声明无参版本的终止函数,否则参数包为空时就找不到对应的类/函数了
- 完美转发的必要性:参数包扩展时,为了保证参数左右值属性不丢失,必须使用完美转发
std::forward, 否则右值参数会被转化为左值,影响移动语义等特性
更多推荐


所有评论(0)