C++/C++11中变长参数的使用

来源:互联网 发布:linux cp命令 目录 编辑:程序博客网 时间:2024/05/23 23:39

C++/C++11中的变长参数可以应用在宏、函数、模板中。

1. 宏:在C99标准中,程序员可以使用变长参数的宏定义。变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,而预定义宏__VA_ARGS__则可以在宏定义的实现部分替换省略号所代表的字符串。

printf函数使用了C语言的函数变长参数特性,通过使用变长函数(variadic function),printf的实现能够接受任何长度的参数列表,如下:

int printf ( const char * format, ... );
可变参数宏是C语言与C++语言的函数宏的参数个数可以是0个或多个。这一语言特性由C99引入,C++11也支持。

可变参数宏的声明语法类似于可变参数函数:逗号后面三个句点”…”,表示一个或多个参数。但常见编译器也允许传递0个参数。宏扩展时使用特殊标识符__VA__ARGS__表示所传递的参数的替换。

对于可变参数为空情形,Visual Studio直接去掉可变参数前面的逗号,GCC需要在__VA__ARGS__前面放上##以去除逗号。

2. 函数:C/C++支持函数的可变参数列表,这个可变参数列表是通过宏来实现的,这些宏定义在头文件stdarg.h中,它是标准库的一部分。这个头文件声明了一个类型va_list和三个宏---- va_start, va_arg, va_end。我们可以声明一个类型为va_list的变量,与这几个宏配合使用,访问参数的值。函数内部必须定义一个类型为va_list的变量vl。然后使用宏va_start、va_arg和va_end来读取,使用va_start宏对vl进行初始化,使得vl成为被传递的变长参数的一个”句柄”(handler)。而后再使用va_arg宏从vl中将参数一一取出用于运算。va_end宏用于结束vl。由于将va_start,va_arg,va_end定义成了宏,可变参数的类型和个数在该函数中完全由程序代码控制,并不能智能地进行识别,所以导致编译器对可变参数的函数原型检查不够严格,难于查错,不利于写出高质量的代码。要创建一个可变参数函数,必须把省略号(…)放到参数列表后面。

一个可变参数函数是指一个函数拥有不定参数,即是它接受一个可变数目的参数。可变参数函数在C语言存在安全问题,如C语言在没有长度检查和类型检查,在传入过少的参数或不符的类型时可能会出现溢位的情况,更可能会被利用为攻击目标。所以,在设计函数时可以先考虑其它替补方案,例如以类型安全的方式----重载。

为了编写处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有的实参类型相同,可以传递一个名为std::initializer_list的标准库类型;如果实参的类型不同,我们可以编写一种特殊的函数,也就是所谓的可变参数模板。

C++还有一种特殊的形参类型(即省略符),这种功能一般只用于与C函数交互的接口程序。省略符形参是为了便于C++程序访问某些特殊的C代码而设置的,这些代码使用了名为varargs的C标准库功能。通常,省略符形参不应用于其它目的。省略符形参应该仅仅用于C和C++通用的类型,特别应该注意的是,大多数类型的对象在传递给省略符形参时都无法正确拷贝。省略符形参只能出现在形参列表的最后一个位置

关于std::initializer_list的使用可以参考: http://blog.csdn.net/fengbingchun/article/details/77938005 

3. 模板:C++11支持C99的变长宏。从C++11开始支持变长模板(variadic template)。在C++11中,”模板参数包”(template parameter pack),是一种新的模板参数类型。有了这样的参数包,类模板就可以接受任意多个参数作为模板参数。编译器可以将多个模板参数打包成为”单个的”模板参数包。与普通的模板参数类似,模板参数包也可以是非类型的。除了类型的模板参数包和非类型的模板参数包,模板参数包还可以是模板类型的。

一个模板参数包在模板推导时会被认为是模板的单个参数(虽然实际上它将会打包任意数量的实参)。为了使用模板参数包,我们总是需要将其解包(unpack)。在C++11中,这通常是通过一个名为包扩展(pack expansion)的表达式来完成。

除了变长的模板类,在C++11中,我们还可以声明变长模板的函数。对于变长模板函数而言,除了声明可以容纳变长个模板参数的模板参数包之外,相应地,变长的函数参数也可以声明成函数参数包(function parameter pack)。在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数(模板参数包没有这样的要求)。

在C++11中,标准定义了7种参数包可以展开的位置:表达式、初始化列表、基类描述列表、类成员初始化列表、模板参数列表、通用属性列表、lambda函数的捕捉列表。其它”地方”则无法展开参数包。

在C++11中,标准还引入了新操作符”sizeof…”,其作用是计算参数包中的参数个数。

一个可变参数模板(variadic template)就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包(parameter packet)。存在两种参数包:模板参数包(template parameter packet),表示零个或多个模板参数;函数参数包(function parameter packet),表示零个或多个函数参数。

我们用一个省略号来指出一个模板参数或函数参数表示一个包。在一个模板参数列表中,class…或typename…指出接下来的参数表示零个或多个类型的列表;一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中,如果一个参数的类型是一个模板参数包,则此参数也是一个函数参数包。

编译器从函数的实参推断模板参数类型。对于一个可变参数模板,编译器还会推断包中参数的数目。

sizeof…运算符:当我们需要知道包中有多少元素时,可以使用sizeof…运算符。类似sizeof,sizeof…也返回一个常量表达式,而且不会对其实参求值。

我们可以使用一个std::initializer_list来定义一个可接受可变数目实参的函数。但是,所有实参必须具有相同的类型(或它们的类型可以转换为同一个公共类型)。当我们既不知道想要处理的实参的数目也不知道它们的类型时,可变参数函数是很有用的。

可变参数函数通常是递归的。第一步调用处理包中的第一个实参,然后用剩余实参调用自身

包扩展:对于一个参数包,除了获取其大小外,我们能对它做的唯一的事情就是扩展(expand)它。当扩展一个包时,我们还要提供用于每个扩展元素的模式(pattern)。扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式右边放一个省略号(…)来触发扩展操作。

在新标准下,我们可以组合使用可变参数模板与forward机制来编写函数,实现将其实参不变地传递给其它函数。

可变参数模板是模板编程时,模板参数(template parameter)的个数可变的情形。C++11之前,模板(类模板与函数模板)在声明时必须有固定数量的模板参数。C++11允许模板定义有任意类型任意数量的模板参数。

省略号(…)在可变参数模板中有两种用途:(1)、省略号出现在形参名字左侧,声明了一个参数包(parameter pack)。使用这个参数包,可以绑定0个或多个模板实参给这个可变模板形参参数包。参数包也可以用于非类型的模板参数。(2)、省略号出现在包含参数包的表达式的右侧,则把这个参数包解开为一组实参,使得在省略号前的整个表达式使用每个被解开的实参完成求值,所有表达式求值结果被逗号分开。注意这里的逗号不是作为逗号运算符,而是用作:A、被逗号分隔开的一组函数调用实参列表(该函数必须是可变参数函数,而不能是固定参数个数的函数);B、被逗号分隔开的一组初始化列表(initializer list);C、被逗号分隔开的一组基类列表(base class list)与构造函数初始化列表(constructor’s initialization list);D、被逗号分隔开的一组函数的可抛出的异常规范(exception specification)的声明列表。实际上,能够接受可变参数个数的参数包展开的场合,必须是能接受任意个数的逗号分隔开的表达式列表。

我们平时经常使用的std::tuple就是一个变长模板类,如下:

template <class... Types> class tuple;
下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference:

#include "variable_arguments.hpp"#include <stdarg.h>#include <cassert>#include <iostream>#include <stdexcept>#include <string>#include <initializer_list>namespace variable_arguments_ {///////////////////////////////////////////////////////////#define LOG(...) { \fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__); \fprintf(stderr, __VA_ARGS__); \fprintf(stderr, "\n"); \}int test_variable_arguments_macro_1(){int value{ 5 };LOG("value = %d", value);return 0;}//////////////////////////////////////////////////// reference: http://www.geeksforgeeks.org/variable-length-arguments-for-macros/#define INFO 1#define ERR 2#define STD_OUT stdout#define STD_ERR stderr#define LOG_MESSAGE(prio, stream, msg, ...) do { \char *str; \if (prio == INFO) \str = "INFO"; \else if (prio == ERR) \str = "ERR"; \fprintf(stream, "[%s] : %s : %d : "msg" \n", \str, __FILE__, __LINE__, ##__VA_ARGS__); \} while (0)int test_variable_arguments_macro_2(){char *s = "Hello";/* display normal message */LOG_MESSAGE(ERR, STD_ERR, "Failed to open file");/* provide string as argument */LOG_MESSAGE(INFO, STD_OUT, "%s Geeks for Geeks", s);/* provide integer as arguments */LOG_MESSAGE(INFO, STD_OUT, "%d + %d = %d", 10, 20, (10 + 20));return 0;}///////////////////////////////////////////////////////////////// reference: https://msdn.microsoft.com/en-us/library/ms177415.aspx#define EMPTY#define CHECK1(x, ...) if (!(x)) { printf(__VA_ARGS__); }#define CHECK2(x, ...) if ((x)) { printf(__VA_ARGS__); }#define CHECK3(...) { printf(__VA_ARGS__); }#define MACRO(s, ...) printf(s, __VA_ARGS__)int test_variable_arguments_macro_3(){CHECK1(0, "here %s %s %s", "are", "some", "varargs1(1)\n");CHECK1(1, "here %s %s %s", "are", "some", "varargs1(2)\n");CHECK2(0, "here %s %s %s", "are", "some", "varargs2(3)\n");CHECK2(1, "here %s %s %s", "are", "some", "varargs2(4)\n");// always invokes printf in the macroCHECK3("here %s %s %s", "are", "some", "varargs3(5)\n");MACRO("hello, world\n");MACRO("error\n", EMPTY);return 0;}///////////////////////////////////////////////////////////////static double SumOfFloat(int count, ...){va_list ap;double sum{ 0.f };va_start(ap, count);for (int i = 0; i < count; ++i) {sum += va_arg(ap, double);}va_end(ap);return sum;}int test_variable_arguments_function_1(){fprintf(stdout, "sum: %f\n", SumOfFloat(3, 1.2f, -2.3f, 5.8f));return 0;}////////////////////////////////////////////////////////template<typename T>T sum(std::initializer_list<T> il){T data(0);for (T i : il)data += i;return data;}int test_variable_arguments_function_2(){std::cout << sum({ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }) << std::endl;return 0;}//////////////////////////////////////////////// reference: https://stackoverflow.com/questions/41400/how-to-wrap-a-function-with-variable-length-argumentsvoid myprintf(char* fmt, ...){va_list args;va_start(args, fmt);vprintf(fmt, args); //  vprint, vsprintf, vfprintf: there are also 'safe' versions in Microsoft's C runtime that will prevent buffer overruns, etc.va_end(args);}int test_variable_arguments_function_3(){int a = 9;int b = 10;char v = 'C';myprintf("This is a number: %d and \nthis is a character: %c and \n another number: %d\n", a, v, b);return 0;}//////////////////////////////////////////////////////////// reference: https://msdn.microsoft.com/en-us/library/fxhdxye9.aspx//  ShowVar takes a format string of the form//   "ifcs", where each character specifies the//   type of the argument in that position.////  i = int//  f = float//  c = char//  s = string (char *)////  Following the format specification is a variable//  list of arguments. Each argument corresponds to//  a format character in the format string to which// the szTypes parameter pointsvoid ShowVar(char* szTypes, ...){va_list vl;int i;//  szTypes is the last argument specified; you must access   //  all others using the variable-argument macros.  va_start(vl, szTypes);// Step through the list.  for (i = 0; szTypes[i] != '\0'; ++i) {union Printable_t {int     i;float   f;char    c;char   *s;} Printable;switch (szTypes[i]) {   // Type to expect.  case 'i':Printable.i = va_arg(vl, int);printf_s("%i\n", Printable.i);break;case 'f':Printable.f = va_arg(vl, double);printf_s("%f\n", Printable.f);break;case 'c':Printable.c = va_arg(vl, char);printf_s("%c\n", Printable.c);break;case 's':Printable.s = va_arg(vl, char *);printf_s("%s\n", Printable.s);break;default:break;}}va_end(vl);}int test_variable_arguments_function_4(){ShowVar("fcsi", 32.4f, 'a', "Test string", 4);return 0;}//////////////////////////////////////////////////// reference: http://www.cplusplus.com/reference/cstdarg/va_arg/int FindMax(int n, ...){int i, val, largest;va_list vl;va_start(vl, n);largest = va_arg(vl, int);for (i = 1; i<n; i++) {val = va_arg(vl, int);largest = (largest>val) ? largest : val;}va_end(vl);return largest;}int test_variable_arguments_function_5(){int m;m = FindMax(7, 702, 422, 631, 834, 892, 104, 772);fprintf(stdout, "The largest value is: %d\n", m);return 0;}///////////////////////////////////////////////////////// reference: 《深入理解C++11----C++11新特性解析与应用》 6.2template <long... nums> struct Multiply;template<long first, long... last>struct Multiply<first, last...> {static const long val = first * Multiply<last...>::val;};template<>struct Multiply<> {static const long val = 1;};int test_variable_arguments_template_class_1(){fprintf(stdout, "%ld\n", Multiply<2, 3, 4, 5>::val); // 120fprintf(stdout, "%ld\n", Multiply<22, 44, 66, 88, 9>::val); // 50599296return 0;}///////////////////////////////////////////////////////////////////void Printf(const char* s){while (*s) {if (*s == '%' && *++s != '%')throw std::runtime_error("invalid format string: missing arguments");std::cout << *s++;}}template<typename T, typename... Args>void Printf(const char* s, T value, Args... args){while (*s) {if (*s == '%' && *++s != '%') {std::cout << value;return Printf(++s, args...);}std::cout << *s++;}throw std::runtime_error("extra arguments provided to Printf");}int test_variable_arguments_template_function_1(){Printf("hello %s\n", std::string("world")); // hello worldreturn 0;}/////////////////////////////////////////////////////////////template<typename... T>void DummyWrapper(T... t) {}template<typename T>T pr(T t){std::cout << t;return t;}template<typename... A>void VTPrint(A... a){DummyWrapper(pr(a)...);}int test_variable_arguments_template_function_2(){VTPrint(1, ", ", 1.2, ", abc\n");return 0;}/////////////////////////////////////////////////////////////// 用来终止递归并打印最后一个元素的函数,此函数必须在可变参数版本的print定义之前声明template<typename T>std::ostream& print(std::ostream& os, const T& t){return os << t; // 包中最后一个元素之后不打印分隔符}// 包中除了最后一个元素之外的其它元素都会调用这个版本的printtemplate<typename T, typename... Args>std::ostream& print(std::ostream& os, const T& t, const Args&... rest){os << t << ", "; // 打印第一个字符return print(os, rest...); // 递归调用,打印其它实参}int test_variable_arguments_template_function_3(){// 对于最后一个调用,两个函数提供同样好的匹配。但是,非可变参数模板比可变参数模板更// 特例化,因此编译器选择非可变参数版本char i{ 'A' }, s{ 'b' };print(std::cout, i, s, 42);// 当定义可变参数版本的print时,非可变参数版本的声明必须在作用域中,否则,可变参数版本会无限递归。return 0;}///////////////////////////////////////////////////////////template<class... A>void Print(A... arg){assert(false);}void Print(int a1, int a2, int a3, int a4, int a5, int a6){fprintf(stdout, "%d, %d, %d, %d, %d, %d\n", a1, a2, a3, a4, a5, a6);}template<class... A>int Vaargs(A... args){int size = sizeof...(A);switch (size) {case 0: Print(99, 99, 99, 99, 99, 99);break;case 1: Print(99, 99, args..., 99, 99, 99);break;case 2: Print(99, 99, args..., 99, 99);break;case 3: Print(args..., 99, 99, 99);break;case 4: Print(99, args..., 99);break;case 5: Print(99, args...);break;case 6: Print(args...);break;default:Print(0, 0, 0, 0, 0, 0);}return size;}int test_variable_arguments_sizeof_1(){Vaargs();Vaargs(1);Vaargs(1, 2);Vaargs(1, 2, 3);Vaargs(1, 2, 3, 4);Vaargs(1, 2, 3, 4, 5);Vaargs(1, 2, 3, 4, 5, 6);Vaargs(1, 2, 3, 4, 5, 6, 7);return 0;}} // namespace variable_arguments_

GitHub: https://github.com/fengbingchun/Messy_Test