文章目录
- 可变模板参数的概念
- 递归函数方式展开参数包
- STL容器中的empalce相关的接口函数
- emplace 与 insert / push_back 的区别
可变模板参数的概念
可变参数模板是 C++11 引入的一种模板特性,允许定义可以接收任意数量参数的模板,广泛应用于函数和类的设计中,以实现灵活和通用的代码结构。
可变参数模板可以处理不定数量的模板参数。它的语法使用了三个点(...
)来表示模板参数包。这使得你可以创建一个模板,它可以适应任意数量的类型或值。
语法形式
template<typename... Args>
void func(Args... args) {
// 函数体
}
Args...
表示一个模板参数包,它可以是任意数量的类型参数。args...
表示一个函数参数包,可以对应多个具体的实参。
上面的参数args
前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N >= 0)个模版参数。我们无法直接获取参数包args
中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]
这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
递归函数方式展开参数包
// 递归终止条件:没有参数的版本
void print() {
std::cout << "End of recursion" << std::endl;
}
// 可变参数模板版本:处理多个参数
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // 递归调用,逐个处理参数
}
int main() {
print(1, 2, 3, "Hello", 4.5);
return 0;
}
print()
是递归的终止条件,用于处理没有参数的情况。- 模板函数
print(T first, Args... rest)
通过递归调用自身来依次处理每个参数,直到只剩下一个参数。
STL容器中的empalce相关的接口函数
在 C++ 的标准模板库(STL)中,emplace
系列接口提供了一种更高效的方式将元素添加到容器中。emplace
的主要优势是,它可以直接在容器内部构造元素,避免了额外的拷贝或移动操作。因此,相比于使用 insert
或 push_back
这样的函数,emplace
系列接口在某些情况下更高效。(但也高效不了多少,因为移动操作的代价足够小)
https://cplusplus.com/reference/vector/vector/emplace_back/
template <class... Args>
void emplace_back (Args&&... args);
emplace
系列函数的主要作用是原地构造元素,而不是首先构造一个临时对象再复制或移动到容器中
原地构造:使用 emplace
,你可以提供构造元素所需的参数,容器会直接在内部空间中构造该对象,避免了先创建对象再将其复制到容器的过程。
避免不必要的拷贝/移动:与 push_back()
等函数相比,emplace
可以减少临时对象的构造以及拷贝/移动操作,这在性能敏感的场景中很有帮助。
hyt::string
是我自己模拟实现的一个string类,可以看到,我们使用emplace
系列的接口,确实可以将移动拷贝省略掉。能提高一点效率,但其实移动构造的效率并不大,下面我分享一下我自己模拟写的emplace系列接口:
// 模拟实现list在之前的章节有提过,这里只是将原来的代码多增加一些接口的片段代码
// 这是list需要用到的节点类
template<class T>
struct __list_node
{
__list_node(const T& val = T())
:_data(val), _prev(nullptr), _next(nullptr)
{}
// 这里需要在原来的基础上需要增加一个可变模板参数模板的构造函数,方便下面使用new
template<class ...Args>
__list_node(Args&& ...args)
: _data(std::forward<Args>(args)...), _prev(nullptr), _next(nullptr)
{}
T _data;
__list_node* _prev;
__list_node* _next;
};
template<class T>
struct list
{
template<class ...Args>
iterator emplace(iterator position, Args&&... args)
{
node* cur = position._node;
node* prev = cur->_prev;
// 函数参数包的完美转发
node* newnode = new node(std::forward<Args>(args)...);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(cur);
}
template<class ...Args>
void emplace_back(Args&&... args)
{
// 函数参数包的完美转发
emplace(end(), std::forward<Args>(args)...);
}
private:
__list_node<T>* _head; // 指向节点类的指针
// 获取节点函数,这里更新成了万能引用版的
template<class T>
node* get_node(T&& val = T())
{
node* new_node = new node(std::forward<T>(val)); // 完美转发
new_node->_prev = new_node;
new_node->_next = new_node;
return new_node;
}
}
emplace 与 insert / push_back 的区别
最后再来总结一下:何时使用 emplace
效率优先:如果添加对象时想避免额外的构造和拷贝,emplace 通常是更优的选择。
构造复杂对象:当元素的构造比较复杂时,emplace 可以让代码更简洁,直接传入构造参数即