- 内容提要
- 前言
- 第 1 章 预备知识
- 第 2 章 开始学习 C++
- 第 3 章 处理数据
- 第 4 章 复合类型
- 第 5 章 循环和关系表达式
- 第 6 章 分支语句和逻辑运算符
- 第 7 章 函数——C++的编程模块
- 第 8 章 函数探幽
- 第 9 章 内存模型和名称空间
- 第 10 章 对象和类
- 第 11 章 使用类
- 第 12 章 类和动态内存分配
- 第 13 章 类继承
- 第 14 章 C++中的代码重用
- 第 15 章 友元、异常和其他
- 第 16 章 string 类和标准模板库
- 第 17 章 输入、输出和文件
- 第 18 章 探讨 C++新标准
- 附录 A 计数系统
- 附录 B C++保留字
- 附录 C ASCII 字符集
- 附录 D 运算符优先级
- 附录 E 其他运算符
- 附录 F 模板类 string
- 附录 G 标准模板库方法和函数
- 附录 H 精选读物和网上资源
- 附录 I 转换为 ISO 标准 C++
- 附录 J 复习题答案
18.6 可变参数模板
可变参数模板(variadic template)让您能够创建这样的模板函数和模板类,即可接受可变数量的参数。这里介绍可变参数模板函数。例如,假设要编写一个函数,它可接受任意数量的参数,参数的类型只需是 cout 能够显示的即可,并将参数显示为用逗号分隔的列表。请看下面的代码:
这里的目标是,定义 show_list( ),让上述代码能够通过编译并生成如下输出:
要创建可变参数模板,需要理解几个要点:
- 模板参数包(parameter pack);
- 函数参数包;
- 展开(unpack)参数包;
- 递归。
18.6.1 模板和函数参数包
为理解参数包的工作原理,首先来看一个简单的模板函数,它显示一个只有一项的列表:
在上述定义中,有两个参数列表。模板参数列表只包含 T,而函数参数列表只包含 value。下面的函数调用将模板参数列表中的 T 设置为 double,将函数参数列表中的 value 设置为 2.15:
C++11 提供了一个用省略号表示的元运算符(meta-operator),让您能够声明表示模板参数包的标识符,模板参数包基本上是一个类型列表。同样,它还让您能够声明表示函数参数包的标识符,而函数参数包基本上是一个值列表。其语法如下:
其中,Args 是一个模板参数包,而 args 是一个函数参数包。与其他参数名一样,可将这些参数包的名称指定为任何符合 C++标识符规则的名称。Args 和 T 的差别在于,T 与一种类型匹配,而 Args 与任意数量(包括零)的类型匹配。请看下面的函数调用:
在这种情况下,参数包 Args 包含与函数调用中的参数匹配的类型:char、int、const char *和 double。
下面的代码指出 value 的类型为 T:
同样,下面的代码指出 args 的类型为 Args:
更准确地说,这意味着函数参数包 args 包含的值列表与模板参数包 Args 包含的类型列表匹配—无论是类型还是数量。在上面的示例中,args 包含值‘S’、80、“sweet”和 4.5。
这样,可变参数模板 show_list1( ) 与下面的函数调用都匹配:
就最后一个函数调用而言,模板参数包 Args 包含类型 int、int、int、int、const char *和 std::string,而函数参数包 args 包含值 2、4、6、8、“who do we”和 std::string(“appreciate”)。
18.6.2 展开参数包
但函数如何访问这些包的内容呢?索引功能在这里不适用,即您不能使用 Args[2]来访问包中的第三个类型。相反,可将省略号放在函数参数包名的右边,将参数包展开。例如,请看下述有缺陷的代码:
这是什么意思呢?为何说它存在缺陷?假设有如下函数调用:
这将把 5、‘L’和 0.5 封装到 args 中。在该函数内部,下面的调用:
将展开成如下所示:
也就是说,args 被替换为三给存储在 args 中的值。因此,表示法 args...展开为一个函数参数列表。不幸的是,该函数调用与原始函数调用相同,因此它将使用相同的参数不断调用自己,导致无限递归(这存在缺陷)。
18.6.3 在可变参数模板函数中使用递归
虽然前面的递归让 show_list1( ) 成为有用函数的希望破灭,但正确使用递归为访问参数包的内容提供了解决方案。这里的核心理念是,将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。与常规递归一样,确保递归将终止很重要。这里的技巧是将模板头改为如下所示:
对于上述定义,show_list3( ) 的第一个实参决定了 T 和 value 的值,而其他实参决定了 Args 和 args 的值。这让函数能够对 value 进行处理,如显示它。然后,可递归调用 show_list3( ),并以 args…的方式将其他实参传递给它。每次递归调用都将显示一个值,并传递缩短了的列表,直到列表为空为止。程序清单 18.9 提供了一种实现,它虽然不完美,但演示了这种技巧。
程序清单 18.9 variadic1.cpp
1.程序说明
请看下面的函数调用:
第一个实参导致 T 为 double,value 为 x*x。其他三种类型(char、int 和 std::string)将放入 Args 包中,而其他三个值(‘!’、7 和 mr)将放入 args 包中。
接下来,函数 show_list3( ) 使用 cout 显示 value(大约为 7.38905)和字符串“, ”。这完成了显示列表中第一项的工作。
接下来是下面的调用:
考虑到 args…的展开作用,这与如下代码等价:
前面说过,列表将每次减少一项。这次 T 和 value 分别为 char 和‘!’,而余下的两种类型和两个值分别被包装到 Args 和 args 中,下次递归调用将处理这些缩小了的包。最后,当 args 为空时,将调用不接受任何参数的 show_list3( ),导致处理结束。
程序清单 18.9 中两个函数调用的输出如下:
2.改进
可对 show_list3( ) 做两方面的改进。当前,该函数在列表的每项后面显示一个逗号,但如果能省去最后一项后面的逗号就好了。为此,可添加一个处理一项的模板,并让其行为与通用模板稍有不同:
这样,当 args 包缩短到只有一项时,将调用这个版本,而它打印换行符而不是逗号。另外,由于没有递归调用 show_list3( ),它也将终止递归。
另一个可改进的地方是,当前的版本按值传递一切。对于这里使用的简单类型来说,这没问题,但对于 cout 可打印的大型类来说,这样做的效率很低。在可变参数模板中,可指定展开模式(pattern)。为此,可将下述代码:
替换为如下代码:
这将对每个函数参数应用模式 const &。这样,最后分析的参数将不是 std::string mr,而是 const std::string& mr。
程序清单 18.10 包含这两项修改。
程序清单 18.10 variadic2.cpp
该程序的输出如下:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论