C++11可变参数模板、包扩展和emplace系列接口详解

可变参数模板

4.1 基本语法及原理

基本语法

我们用省略号来指出一个模板参数或函数参数的表示一个包,在模板参数列表中,class...typename...指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。

template <class ...Args> 
void Func(Args... args) {}  // 值传递版本

template <class ...Args> 
void Func(Args&... args) {} // 左值引用版本

template <class ...Args> 
void Func(Args&&... args) {} // 通用引用版本(可接受左值或右值)

参数包类型

有两种参数包:

  1. 模板参数包:表示零个或多个模板参数
  2. 函数参数包:表示零个或多个函数参数

计算参数包大小

可以使用sizeof...运算符计算参数包中参数的个数:

template <class ...Args>
void Print(Args&&... args) {
    cout << sizeof...(args) << endl;  // 打印参数个数
}

原理

可变参数模板的原理与普通模板类似,编译器会根据实际调用情况实例化出对应参数个数和类型的函数。例如:

// 通过参数包个数实例化出函数模板 
template <class T1>
void Print(T1&& arg1);
template <class T1, class T2>
void Print(T1&& arg1, T2&& arg2);
template <class T1, class T2, class T3>
void Print(T1&& arg1, T2&& arg2, T3&& arg3);
//编译本质这⾥会结合引⽤折叠规则实例化出以下四个函数 
Print(1);                    // 实例化 Print(int&&)
Print(1, string("xxxxx"));   // 实例化 Print(int&&, string&&)
Print(1.1, string("xxxxx"), x); // 实例化 Print(double&&, string&&, double&)

4.2 包扩展

包扩展是将参数包分解并应用模式的过程,通过在模式右边加...来触发。

递归展开示例

在这里插入图片描述
输出结果
在这里插入图片描述

// 本质编译器将可变参数模板通过模式的包扩展,编译器推导的以下三个重载函数函数 
//void ShowList(double x)
//{
// 	cout << x << " ";
// 	ShowList();
//}
//
//void ShowList(string x, double z)
//{
// 	cout << x << " ";
// 	ShowList(z);
//}
//
//void ShowList(int x, string y, double z)
//{
// 	cout << x << " ";
// 	ShowList(y, z);
//}
//void Print(int x, string y, double z)
//{
// 	ShowList(x, y, z);
//}

复杂包扩展

可以直接将参数包作为另一个函数的参数:

//GetArg(args)... 这是参数包展开的语法。对于Print(x,y,z)的调用,它会被展开为:
//GetArg(x), GetArg(y), GetArg(z)
template <class T>
const T& GetArg(const T& x) {
    cout << x << " ";
    return x;
}

template <class ...Args>
void Arguments(Args... args) {}

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

本质可以理解为编译器编译时,包的扩展模式
将上面的函数模板扩展实例化为下面的函数
这相当于编译器生成了类似下面的代码:

void Print(int x, string y, double z) {
    Arguments(GetArg(x), GetArg(y), GetArg(z));
}
//可以把...想象成一个"解包"操作符,它会把参数包中的每个元素都应用到前面的表达式
//这里是GetArg()上。这种模式在函数式编程中类似于"map"操作。

4.3 emplace系列接口

C++11为STL容器新增了emplace系列接口,它们使用可变参数模板实现更高效的插入操作。

emplace_back示例

list<pair<string, int>> lt;

// 传统方式:构造pair + 拷贝/移动构造
pair<string, int> kv("苹果", 1);
lt.push_back(kv);          // 拷贝构造
lt.push_back(move(kv));    // 移动构造

// emplace方式:直接构造
lt.emplace_back("苹果", 1); // 直接在容器内构造pair

emplace实现原理

emplace接口通过将参数包一直传递到容器内部节点构造中:
使用完美转发std::forward保持参数的值类别(左值/右值)

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

template <class... Args>
iterator insert(iterator pos, Args&&... args) {
    Node* newnode = new Node(std::forward<Args>(args)...);
    // ... 插入操作
}

为什么emplace更高效

  1. 避免临时对象:可以直接在容器内部构造对象,省去了构造临时对象再拷贝/移动的开销

  2. 灵活性:可以直接传递构造对象所需的参数,而不需要先构造对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值