目录
可变参数模板
函数递归展开参数包
逗号表达式展开参数包
emplace_back
可变参数模板
其实C语言中我们就一直在使用可变参数列表。
C++11 的新特性可变参数模板能够让我们创建可变参数的函数模板和类模板,相比C++98和C++03,类模板和函数模板中只能传入固定数量的模板参数,可变模板参数无疑是一个巨大的改进。
然而可变模板参数比较抽象,使用起来需要一定技巧,现阶段我们掌握一些基础的可变参数模板特性就够了。
下面就是一个基本的可变参数的函数模板:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
那我们可变参数模板传参的形式就是这样的:
上面的参数 args 前面有省略号,所以它是一个可变参数模板,我们把带省略号的参数称为"参数包",它里面包含了0至N(N>=0)个模板参数。我们无法直接获取参数包args中的每个参数,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模板参数的一个主要特点,也是最大的难点。
那我们使用模板参数包如何取出参数呢?
函数递归展开参数包
我们可以使用 sizeof 得出参数包中的参数个数:
注意,这里不要想当然使用for循环取参数,因为语法不支持使用args[i]的方式获取可变参数。
方法如下:
再多添加一个模板参数 T ,然后取出 T 的数据,将参数包作为参数递归调用ShowList,这样每次都能取出参数包中的第一个参数,然后我们再编写一个空参数的ShowList,用于当参数包中没有参数时,进行递归的终止。
代码如下:
那如果这里我只有一个参数包,没有额外的模板 T,那应该咋展开呢?
逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接再 expand函数体中展开的,PrintArg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
这种写法中的逗号表达式:(PrintArg(args,0)),先执行ParintArg(args),再得到逗号表达式的结果0。同时还用到了C++11中另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组,{(ParintArg(args),0)...}将会展开成(ParintArg(args1),0)、(ParintArg(args2),0)、(ParintArg(args3),0)……最终会创建一个元素值都为0的数组 int arr[sizeof...(Args)]。由于是逗号表达式,再创建数组的过程中会先执行逗号表达式前面的部分PrintArg(Args)打印出参数,也就是说在构造 int 数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程中展开参数包。
写法如下:
还可以进行简化,不使用逗号表达式。
因为数组a是使用参数包进行构造的,我们直接让参数包调用PrintArg函数,然后函数的返回值设置为0,用于数组的初始化,也可以完成该效果。
如果想打印参数的类型,直接使用typeid(x).name()即可。
逗号表达式这种写法了解一下就行了,一般不会使用到。
emplace_back
接下来我们看 vector 中的emplace_back接口。
如果vector中存放的是 int,emplace_back 和 push_back 是没有任何区别的。
那如果存放的是pair这种类型,
emplace_back插入则不用调用make_pair或初始化列表构造pair对象
我们来分析push_back和emplace_back两者的效率:
push_back插入需要调用make_pair或初始化列表构造pair对象,然后使用拷贝构造或移动构造
而emplace_back直接传入pair的参数包,当插入pair类型时,其底层就直接调用了一次构造便完成了数据的插入,
这也就是说为什么有些场景下emplace_back比push_back更加高效的原因。
接下来我们写一个Date类来测试:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year), _month(month), _day(day)
{
cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
}
Date(const Date &d)
: _year(d._year), _month(d._month), _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
Date &operator=(const Date &d)
{
cout << "Date& operator=(const Date& d))" << endl;
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
list<Date> lt1;
lt1.push_back(Date(2022, 11, 16));
cout << "---------------------------------" << endl;
lt1.emplace_back(2022, 11, 16);
return 0;
}
测试环境为gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC),因为编译器和编译器版本的不同,底层vector实现会不同。
结果(push_back调用了构造和拷贝构造,emplace_back调用了一次构造):
使用 list 同样可以完成该测试。
emplace函数:
emplace和emplace的区别就相当于push_back和insert的区别,即可以在指定位置进行插入。