一、模板的可变参数
C++11的新特性可变参数模板能够让您创建可以接受可变参数数量的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。
C++11中提供了一个用省略号...
表示的元运算符,让你能够声明表示模板参数包的标识符,模板参数包实际上是一个类型列表。
下面就是一个基本可变参数的函数模板:
template <class ...Args>
void ShowList(Args... args)
{}
普通的模板函数:
template<class T>
void showlist(T value)
{}
Args
是一个模板参数包,args
是一个函数形参参数包。
与其他参数名一样,可以将这些参数包的名称指定为任何符合C++标识符规则的名称,Args与T的区别在于,T与一种类型匹配,而Args与任意数量(包括0)的类型匹配。
更准确的说,函数参数包args包含的值列表与模板参数包Args包含的类型列表匹配——无论是从数量还是类型上!
我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。
template <class ...Args>
void ShowList(Args... args)
{
// 获取、解析可变参数
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
并不支持这样使用:
template <class ...Args>
void ShowList(Args... args)
{
cout << sizeof...(Args) << endl;
cout << sizeof...(args) << endl;
for (int i = 0; i < sizeof...(args); i++)
{
// 无法编译,编译器无法解析
cout << args[i] << " ";
}
}
下标索引的方式不能用,如何获取参数包里面的数据呢?
二、展开参数包
可以将省略号放在函数参数包名字的右边,将参数包展开:
template<class T>
void showlist(Args...args)
{
showlist(args...);
}
但是这样调用存在缺陷,假如有这样的调用:
showlist(6,'L',0.6);
这将把6、‘L’、0.6 封装到args中,在该函数内部,下面的调用:
showlist(args...);
将展开成这样:
showlist(5,'L',0.5);
该函数调用与原始函数调用相同,因此他将使用相同的参数不断的调用自己,导致无限递归。
三、递归函数方式展开参数包
要求:解析并打印每个参数包中的类型及其值
增加一个模板参数,我们还是要将函数参数包展开,对列表的第一项进行处理,再将余下的内容传递给递归调用处理,以此类推,直到列表为空。与常规的递归一样,确保递归的终止很重要。
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl << endl;
}
// 展开函数
template <class T,class ...Args>
void ShowList(T val,Args... args)
{
cout << typeid(val).name() <<":"<<val << endl;
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
对于上述定义,第一个实参决定了T
和value
的类型,而其他实参决定了Args
和args
的值,这让函数能够对value
进行处理,然后可以递归调用showlist()并且以args...
的方式将其他实参传递给它,每次递归调用都将显式一个值,并且缩短了列表,直到列表为空。
四、逗号表达式展开参数包
这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。
expand函数中的逗号表达式:(printarg(args), 0)
,也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。
template <class T>
void PrintArg(T t)
{
cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
int arr[] = { (PrintArg(args), 0)... };
cout << endl;
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
五、C++11STL库新增函数接口
STL中push_back接口
STL中emplace_back接口
int main()
{
std::list< std::pair<int, char> > mylist;
mylist.push_back(make_pair(1, 'A'));
mylist.emplace_back(make_pair(1, 'A'));
mylist.emplace_back(1, 'A');
for (auto e : mylist)
cout << e.first << ":" << e.second << endl;
}
对比push_back和emplace_back接口
下面我们试一下带有拷贝构造和移动构造的sjj::string,我们会发现其实差别也不到,emplace_back是直接构造了,push_back是先构造,再移动构造,其实也还好。
int main()
{
std::list< std::pair<int, sjj::string> > mylist;
mylist.emplace_back(10, "sort");
mylist.emplace_back(make_pair(20, "sort"));
cout << endl << endl;
mylist.push_back(make_pair(30, "sort"));
mylist.push_back({ 40, "sort" });
return 0;
}