应用程序库对I/O设备的选择:捆绑到特定的I/O会限制库的灵活性
用类表示概念:设计一个类表示任意I/O接口
使应用程序性与I/O之间的耦合度降低
问题
表示变长字符串的String类,应该能够用关惯用符号打印String:
cout << fullname << endl;
传统方法:
|
|
- 两个潜在的严重问题:
- 使用String类的同时不得不使用iostream类
- 不是用iostream类而使用另一种I/O机制的程序也不得不包括两个完整的I/O库,带来不必要的空间开销
- 没有简单方法在另一种I/O机制中打印String
- 使用专用库如用于进程间通信的库,就很难用这个库打印String的内容
解决方案1:技巧加蛮力
具体问题具体对待:通过将依赖于iostream库的代码划分为各个编译模块,从而把iostream库带来的开销从String中分离出来
String.h头文件
|
|
- 没有ostream的定义
- 要打印String就必须包含iostream.h
|
|
- operator的定义也必须包含iostream.h
|
|
单独编译这个函数可以避免iostream类带来的开销
不能解决另一个问题:在不是ostream的I/O机制上打印String
解决方案2:抽象输出
- String类设计者不可能知道怎样对某种可能的输出设备产生输出
I/O库作者不可能知道每一种将会依赖于该I/O库的对象类型
抽象出输出操作的最基本的部分,提供连接String和I/O库的纽带
dest.send(ptr, n);
- 不同的dest:需要一个类继承体系
- send()纯虚函数,虚析构函数
|
|
- 将类的声明加入Writer.h中,定义通用String输出函数
|
|
- 运行得较慢
- 令该函数为String类的友元函数,获得String的特殊实现细节,一次调用send发送完整的String
String类必须知道Writer是如何工作的,仍存在不适当的耦合
为特定的目的地定义特定的Writer类
为C类型的FILE指针定义的Writer类
|
|
- 在stdout中写String
|
|
定义一个表示写任意字符序列到任意目的地的抽象积累Writer
使用继承为每个向使用的I/O库创建一个特殊的Writer类
为每个知道如何使用何种Writer写该类对象的应用类定义输出操作
解决方案3:技巧而无蛮力
- 设计一个小I/O库,唯一目的是作为其他I/O库的接口
- 目前的不足之处:应用库需要知道Writer类本身特性
不能编写:
String hello = "Hello\n", goodbye = "Goodbye\n";
FileWriter(stdout) << hello << goodbye;
FileWriter(stdout)
是一个临时值,第一个<<
完成后可能就被销毁- 在某些C++编译器实现中会出现这种情况
一种方法:禁止Writer生成临时对象(可以通过显式声明Writer类做到)
另一种方法:完全不用引用
- 句柄类:WriterSurrogate
- 指向某个继承自类Writer的底层类
新方法:利用编译期间能得到的关于类的信息
将write当作一个概念,一个write属于一个类型族
不使send作为成员函数:
send(dest, ptr, n);
- 把写入程序与应用类联系起来
|
|
- 常规的方式访问stdio
|
|
- 可以以如下方式调用
String hello = "Hello\n", goodbye = "Goodbye\n";
FileWriter(stdout) << hello << goodbye;
提供I/O独立性方式:
约定应用程序中的类采用使用send模板进行所有的输出操作
每个I/O包都需要一个专为写的适当的send函数(用户提供)
应用程序的库除了要知道如何使用send函数,不必知道任何输出操作的细节
Summary
模板可以提供对操作进行抽象化的方法,就像类对数据结构提供抽象化
可以在编译时利用已知的特定I/O包的信息,而不必等到执行时
- 编译时就进行类型检查
- 避免动态绑定在运行时的开销
《C++沉思录(Cplusplus Thinking)》笔记