- 内容提要
- 前言
- 第 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.5 包装器
C++提供了多个包装器(wrapper,也叫适配器[adapter])。这些对象用于给其他编程接口提供更一致或更合适的接口。例如,第 16 章讨论了 bind1st 和 bind2ed,它们让接受两个参数的函数能够与这样的 STL 算法匹配,即它要求将接受一个参数的函数作为参数。C++11 提供了其他的包装器,包括模板 bind、men_fn 和 reference_wrapper 以及包装器 function。其中模板 bind 可替代 bind1st 和 bind2nd,但更灵活;模板 mem_fn 让您能够将成员函数作为常规函数进行传递;模板 reference_wrapper 让您能够创建行为像引用但可被复制的对象;而包装器 function 让您能够以统一的方式处理多种类似于函数的形式。
下面更详细地介绍包装器 function 及其解决的问题。
18.5.1 包装器 function 及模板的低效性
请看下面的代码行:
ef 是什么呢?它可以是函数名、函数指针、函数对象或有名称的 lambda 表达式。所有这些都是可调用的类型(callable type)。鉴于可调用的类型如此丰富,这可能导致模板的效率极低。为明白这一点,来看一个简单的案例。
首先,在头文件中定义一些模板,如程序清单 18.6 所示。
程序清单 18.6 somedefs.h
模板 use_f 使用参数 f 表示调用类型:
接下来,程序清单 18.7 所示的程序调用模板函数 use_f( )6 次。
程序清单 18.7 callable.cpp
在每次调用中,模板参数 T 都被设置为类型 double。模板参数 F 呢?每次调用时,F 都接受一个 double 值并返回一个 double 值,因此在 6 次 use_of( ) 调用中,好像 F 的类型都相同,因此只会实例化模板一次。但正如下面的输出表明的,这种想法太天真了:
模板函数 use_f( ) 有一个静态成员 count,可根据它的地址确定模板实例化了多少次。有 5 个不同的地址,这表明模板 use_f( ) 有 5 个不同的实例化。
为了解其中的原因,请考虑编译器如何判断模板参数 F 的类型。首先,来看下面的调用:
其中的 dub 是一个函数的名称,该函数接受一个 double 参数并返回一个 double 值。函数名是指针,因此参数 F 的类型为 double(*) (double):一个指向这样的函数的指针,即它接受一个 double 参数并返回一个 double 值。
下一个调用如下:
第二个参数的类型也是 double(*) (double),因此该调用使用的 use_f( ) 实例化与第一个调用相同。
在接下来的两个 use_f( ) 调用中,第二个参数为对象,F 的类型分别为 Fp 和 Fq,因为将为这些 F 值实例化 use_f( ) 模板两次。最后,最后两个调用将 F 的类型设置为编译器为 lambda 表达式使用的类型。
18.5.2 修复问题
包装器 function 让您能够重写上述程序,使其只使用 use_f( ) 的一个实例而不是 5 个。注意到程序清单 18.7 中的函数指针、函数对象和 lambda 表达式有一个相同的地方,它们都接受一个 double 参数并返回一个 double 值。可以说它们的调用特征标(call signature)相同。调用特征标是有返回类型以及用括号括起并用头号分隔的参数类型列表定义的,因此,这六个实例的调用特征标都是 double (double)。
模板 function 是在头文件 functional 中声明的,它从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象或 lambda 表达式。例如,下面的声明创建一个名为 fdci 的 function 对象,它接受一个 char 参数和一个 int 参数,并返回一个 double 值:
然后,可以将接受一个 char 参数和一个 int 参数,并返回一个 double 值的任何函数指针、函数对象或 lambda 表达式赋给它。
在程序清单 18.7 中,所有可调用参数的调用特征标都相同:double (double)。要修复程序清单 18.7 以减少实例化次数,可使用 function<double(double)>创建六个包装器,用于表示 6 个函数、函数符和 lambda。这样,在对 use_f( ) 的全部 6 次调用中,让 F 的类型都相同(function<double(double)>),因此只实例化一次。据此修改后的程序如程序清单 18.8 所示。
程序清单 18.8 wrapped.cpp
下面是该程序的示例输出:
从上述输出可知,count 的地址都相同,而 count 的值表明,use_f( ) 被调用了 6 次。这表明只有一个实例,并调用了该实例 6 次,这缩小了可执行代码的规模。
18.5.3 其他方式
下面介绍使用 function 可完成的其他两项任务。首先,在程序清单 18.8 中,不用声明 6 个 function<double (double)>对象,而只使用一个临时 function<double (double)>对象,将其用作函数 use_f( ) 的参数:
其次,程序清单 18.8 让 use_f( ) 的第二个实参与形参 f 匹配,但另一种方法是让形参 f 的类型与原始实参匹配。为此,可在模板 use_f( ) 的定义中,将第二个参数声明为 function 包装器对象,如下所示:
这样函数调用将如下:
参数 dub、Fp(5.0) 等本身的类型并不是 function<double(double)>,因此在 use_f 后面使用了<double>来指出所需的具体化。这样,T 被设置为 double,而 std::function<T(T)>变成了 std::function<double(double)>。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论