C++11新特性:9—— C++11在函数模板和类模板中使用可变参数
Posted 贺二公子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11新特性:9—— C++11在函数模板和类模板中使用可变参数相关的知识,希望对你有一定的参考价值。
原文地址:http://c.biancheng.net/view/vip_8692.html
所谓可变参数,指的是参数的个数和类型都可以是任意的。提到参数,大家会第一时间想到函数参数,除此之外 C++ 的模板(包括函数模板和类模板)也会用到参数。
对于函数参数而言,C++ 一直都支持为函数设置可变参数,最典型的代表就是 printf() 函数,它的语法格式为:
int printf ( const char * format, ... );
...
就表示的是可变参数,即 printf() 函数可以接收任意个参数,且各个参数的类型可以不同,例如:
printf("%d", 10);
printf("%d %c",10, 'A');
printf("%d %c %f",10, 'A', 1.23);
我们通常将容纳多个参数的可变参数称为参数包。借助 format 字符串,printf() 函数可以轻松判断出参数包中的参数个数和类型。
下面的程序中,自定义了一个简单的可变参数函数:
#include <iostream>
#include <cstdarg>
//可变参数的函数
void vair_fun(int count, ...)
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i)
int arg = va_arg(args, int);
std::cout << arg << " ";
va_end(args);
int main()
//可变参数有 4 个,分别为 10、20、30、40
vair_fun(4, 10, 20, 30,40);
return 0;
程序中的 vair_fun() 函数有 2 个参数,一个是 count,另一个就是 … 可变参数。我们可以很容易在函数内部使用 count 参数,但要想使用参数包中的参数,需要借助<cstdarg>
头文件中的 va_start、va_arg 以及 va_end 这 3 个带参数的宏:
- va_start(args, count):args 是 va_list 类型的变量,我们可以简单的将其视为 char * 类型。借助 count 参数,找到可变参数的起始位置并赋值给 args;
- va_arg(args, int):调用 va_start 找到可变参数起始位置的前提下,通过指明参数类型为 int,va_arg 就可以将可变参数中的第一个参数返回;
- va_end(args):不再使用 args 变量后,应及时调用 va_end 宏清理 args 变量。
注意,借助 va_arg 获取参数包中的参数时,va_arg 不具备自行终止的能力,所以程序中借助 count 参数控制 va_arg 的执行次数,继而将所有的参数读取出来。控制 va_arg 执行次数还有其他方法,比如读取到指定数据时终止。
使用 … 可变参数的过程中,需注意以下几点:
- … 可变参数必须作为函数的最后一个参数,且一个函数最多只能拥有 1 个可变参数。
- 可变参数的前面至少要有 1 个有名参数(例如上面例子中的 count 参数);
- 当可变参数中包含 char 类型的参数时,va_arg 宏要以 int 类型的方式读取;当可变参数中包含 short 类型的参数时,va_arg 宏要以 double 类型的方式读取。
需要注意的是,… 可变参数的方法仅适用于函数参数,并不适用于模板参数。C++11 标准中,提供了一种实现可变模板参数的方法。
可变参数模板
C++ 11 标准发布之前,函数模板和类模板只能设定固定数量的模板参数。C++11 标准对模板的功能进行了扩展,允许模板中包含任意数量的模板参数,这样的模板又称可变参数模板。
1) 可变参数函数模板
先讲解函数模板,如下定义了一个可变参数的函数模板:
template<typename... T>
void vair_fun(T...args)
//函数体
模板参数中, typename(或者 class)后跟 … 就表明 T 是一个可变模板参数,它可以接收多种数据类型,又称模板参数包。vair_fun() 函数中,args 参数的类型用 T… 表示,表示 args 参数可以接收任意个参数,又称函数参数包。
这也就意味着,此函数模板最终实例化出的 vair_fun() 函数可以指定任意类型、任意数量的参数。例如,我们可以这样使用这个函数模板:
vair_fun();
vair_fun(1, "abc");
vair_fun(1, "abc", 1.23);
使用可变参数模板的难点在于,如何在模板函数内部“解开”参数包(使用包内的数据),这里给大家介绍两种简单的方法。
【递归方式解包】
先看一个实例:
#include <iostream>
using namespace std;
//模板函数递归的出口
void vir_fun()
template <typename T, typename... args>
void vir_fun(T argc, args... argv)
cout << argc << endl;
//开始递归,将第一个参数外的 argv 参数包重新传递给 vir_fun
vir_fun(argv...);
int main()
vir_fun(1, "http://www.biancheng.net", 2.34);
return 0;
执行结果为:
1
http://www.biancheng.net
2.34
分析一个程序的执行流程:
- 首先,main() 函数调用 vir_fun() 模板函数时,根据所传实参的值,可以很轻易地判断出模板参数 T 的类型为 int,函数参数 argc 的值为 1,剩余的模板参数和函数参数都分别位于 args 和 argv 中;
- vir_fun() 函数中,首先输出了 argc 的值(为 1),然后重复调用自身,同时将函数参数包 argv 中的数据作为实参传递给形参 argc 和 argv;
- 再次执行 vir_fun() 函数,此时模板参数 T 的类型为 char*,输出 argc 的值为 “http:www.biancheng.net”。再次调用自身,继续将 argv 包中的数据作为实参;
- 再次执行 vir_fun() 函数,此时模板参数 T 的类型为 double,输出 argc 的值为 2.34。再次调用自身,将空的 argv 包作为实参;
- 由于 argv 包没有数据,此时会调用无任何形参、函数体为空的 vir_fun() 函数,最终执行结束。
以递归方式解包,一定要设置递归结束的出口。例如本例中,无形参、函数体为空的 vir_fun() 函数就是递归结束的出口。
【非递归方法解包】
借助逗号表达式和初始化列表,也可以解开参数包。
以 vir_fun() 函数为例,下面程序演示了非递归方法解包的过程:
#include <iostream>
using namespace std;
template <typename T>
void dispaly(T t)
cout << t << endl;
template <typename... args>
void vir_fun(args... argv)
//逗号表达式+初始化列表
int arr[] = (dispaly(argv),0)... ;
int main()
vir_fun(1, "http://www.biancheng.net", 2.34);
return 0;
这里重点分析一下第 13 行代码,我们以
初始化列表的方式对数组 arr 进行了初始化, (display(argv),0)… 会依次展开为 (display(1),0)、(display(“http://www.biancheng.net”),0) 和 (display(2.34),0)。也就是说,第 13 行代码和如下代码是等价的:
int arr[] = (dispaly(1),0), (dispaly("http://www.biancheng.net"),0), (dispaly(2.34),0) ;
可以看到,每个元素都是一个逗号表达式,以 (display(1), 0) 为例,它会先计算 display(1),然后将 0 作为整个表达式的值返回给数组,因此 arr 数组最终存储的都是 0。arr 数组纯粹是为了将参数包展开,没有发挥其它作用。
2) 可变参数类模板
C++11 标准中,类模板中的模板参数也可以是一个可变参数。C++ 11 标准提供的 tuple 元组类就是一个典型的可变参数模板类,它的定义如下:
template <typename... Types>
class tuple;
和固定模板参数的类不同,tuple 模板类实例化时,可以接收任意数量、任意类型的模板参数,例如:
std:tuple<> tp0;
std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.34);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.34, "http://www.biancheng.net");
如下代码展示了一个支持可变参数的类模板:
#include <iostream>
//声明模板类demo
template<typename... Values> class demo;
//继承式递归的出口
template<> class demo<> ;
//以继承的方式解包
template<typename Head, typename... Tail>
class demo<Head, Tail...>
: private demo<Tail...>
public:
demo(Head v, Tail... vtail) : m_head(v), demo<Tail...>(vtail...)
dis_head();
void dis_head() std::cout << m_head << std::endl;
protected:
Head m_head;
;
int main()
demo<int, float, std::string> t(1, 2.34, "http://www.biancheng.net");
return 0;
程序中,demo 模板参数中的 Tail 就是一个参数包,解包的方式是以“递归+继承”的方式实现的。具体来讲,demo<Head, Tail…> 类实例化时,由于其继承自 demo<Tail…> 类,因此父类也会实例化,一直递归至 Tail 参数包为空,此时会调用模板参数列表为空的 demo 模板类。
程序的输出结果为:
http://www.biancheng.net
2.34
1
可变参数模板类还有其它的解包方法,这里不再一一赘述,感兴趣的读者可以自行做深入的研究。
以上是关于C++11新特性:9—— C++11在函数模板和类模板中使用可变参数的主要内容,如果未能解决你的问题,请参考以下文章