技术(三):应用器、操纵器和函数对象

以类似文件操作的语法,通过某种方式发送一个带外信号(out-of-band signal)给某个类似文件的东西

  • cout << flush 强行立即输出缓冲区数据

  • 基于两种函数的解决方法

    • 操纵器(manipalator):以某种方式作用于由它的参数表示的数据
    • 应用器(applicator):一个重载操作符,操作数为一个可操纵的值和一个将作用于这个值的操纵器
  • 将flush定义为一个操纵器,把<<操作符定义为应用器

扩展:定义一种函数对象得到具有多个参数的应用器

问题

  • C语言printf函数提供输出
    • 违背C语言严格的规定(类型检查不够安全)
    • 可以在不同的时候以不同类型的参数调用
    • 方便用户只用一个单独的输出转换函数而不是不同类型用不同的输出函数
  • C++语言提供比C更严格的类型检查

    • 对于可以写的每种数据类型都有一个单独的程序
    • C++重载机制允许这些(不同的)程序取相同的名字
    • 重载 operator<< 为用户自定义的类型增加输出功能
  • 例:Complex类(可用成员函数re和im获得对象内容的值)对象值的输出

1
2
3
ostream& operator<<(ostream& file, const Complex& z) {
return file << "(" << z.re() << ", " << z.im() << ")";
}
  • 一个函数flush,参数和返回值都是ostream
    • 强迫所有处于等待的缓冲区数据写入文件
1
2
3
4
ostream& flush(ostream& file) {
// 清空输出缓冲区代码
return file;
}
  • 使用: flush(cout << "Password: ");

  • 操纵器:获得并返回对某个类型的引用,而且以某种方式操纵该类型的对象

  • 语法不够优雅简洁

1
2
3
4
5
6
cout << x;
flush(cout);
cout << y;
flush(cout);
cout << x;
flush(cout);
  • 简写为:flush(flush(flush(cout << x) << y) << z);
    • 难以理解

一种解决方案

  • 定义一个FLUSHTYPE的任意类型
    • 和一个该类型的变量
    • 只用于重载 operator<<
1
2
3
4
5
6
class FLUSHTYPE { };
FLUSHTYPE FLUSH;
ostream& operator<<(ostream& ofile, FLUSHTYPE f) {
return flush(ofile);
}
  • 更有意思的用法:
    • cout << x << FLUSH << y << FLUSH << z << FLUSH;

另一种不同的解决方案

  • 前述解决方案存在缺陷
    • 对于每个操纵器必须定义一个类似FLUSHTYPE的新伪类型
      • 操纵器很多时必须定义很多新类型
    • 必须定义一系列类似FLUSH的伪对象(dummy object)

一个可以消除这些额外负担的小技巧:

1
2
3
ostream& operator<<(ostream& ofile, ostream& (*func) (ostream&)) {
return (*func)(ofile);
}
  • 应用器:右操作数是一个操纵器,左操作数是被操纵的对象
    • cout << f; 含义和 f(cout); 相同
  • 输入的情形类似

    • 例如:忽略输入文件中的空格
1
2
3
4
5
6
class whitespace { };
whitespace WS;
istream& operator>>(istream& ifile, whitespace& dummy) {
return eatwhite(ifile);
}
  • 用一个操纵器eatwite忽略文件中的空格
    • cin >> WS; 含义和 eatwhite(cin); 相同
  • 为输入操作定义一个应用器

1
2
3
istream& operator>>(istream& ifile, istream& (*func)(istream&)) {
return (*func)(ifile);
}
  • cin >> eatwhite;
    • 抛弃whitespace和WS的定义以及用whitespace作右参数的>>的定义

多个参数

  • 希望能够对任意函数定义操纵器和应用器以操纵某种类型
  • 例:I/O库提供某个函数用来设置一个与终端相连的流的数据传输速率
    • cout << speed(9600);
  • 定义一种类型容纳一个指向操纵器的指针和一个传给它的参数的指针

  • 整数操纵器

1
2
3
4
ostream& setspeed(ostream& ofile, int n) {
// 设置新的数据传输速率的代码
return ifile;
}
  • 定义一种函数对象包含需要调用的操纵器的信息
1
2
3
4
5
6
7
8
9
10
11
class int_fcn_obj {
public:
int_fcn_obj(ostream& (*f)(ostream&, int), int v): func(f), val(v) { }
ostream& operator()(ostream& o) const {
return (*func)(o, val);
}
private:
ostream& (*func)(ostream&, int);
int val;
};
  • func是一个指向整数操纵器的指针,val是用作操纵器的参数的整数

  • 根据函数对象定义一个应用器

1
2
3
ostream& operator<<(ostream& ofile, const int_fcn_obj& im) {
return im(ofile)
}
  • 定义speed函数使 cout << speed(9600); 可运行
1
2
3
int_fcn_obj speed(int n) {
return int_fcn_obj(setspeed, n);
}

一个例子

  • 提供一个把数字转换成人能读懂的16进制值的函数:cout << to_hex(n);

  • 不依赖于任何字符串库,to_hex返回字符指针:内存何时释放

    • 指针指向静态缓冲区:两次连续调用会覆盖
    • 环形缓冲区:连续调用超过限度也会产生错误
  • 定义一个long_fn_obj的长整型函数对象类和一个长整型的应用器 <<

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class long_fn_obj {
public:
long_fn_obj(ostream& (*f)(ostream&, long), long v): func(f), val(v) { }
ostream& operator() (ostream& o) const {
return (*func)(o, val);
}
private:
ostream& (*func)(ostream&, long);
long val;
};
ostream& operator<<(ostream& ofile, const long_fn_obj& im) {
return im(ofile);
}
  • 定义一个16进制转换操纵器
1
2
3
ostream& hexconv(ostream& ofile, long n) {
return ofile << to_hex(n);
}
  • 重载hexconv生成一个长整型函数对象
1
2
3
long_fn_obj hexconv(long n) {
return long_fn_obj((ostream& (*) (ostream&, long)) hexconv, n);
}
  • 用法:cout << hexconv(m) << " " << hexconv(n);

简化

  • 不同函数对象和应用器的定义都很相似:使用模板

  • 函数对象模板

1
2
3
4
5
6
7
8
9
10
11
12
13
template <class stype, class vtype>
class fcn_obj {
public:
fcn_obj(stype& (*f)(stype&, vtype), vtype v): func(f), val(v) { }
stype& operator() (stype& s) const {
rreturn (*func)(s, val);
}
private:
stype& (*func)(stype&, vtype);
vtype val;
};
  • 应用器模板
1
2
3
4
template <class stype, class vtype>
stype& operator<<(stype& ofile, const fcn_obj<stype, vtype>& im) {
return im(ofile);
}
  • 使用时hexconv必须以模板的形式重写
1
2
3
4
fcn_obj<ostream, long> hexconv(long n) {
ostream (*my_hex) (ostream&, long) = hexconv;
return fcn_obj<ostream, long>(my_hex, n);
}

Summary

语言的使用变得简洁优雅


《C++沉思录(Cplusplus Thinking)》笔记