6.10. 流

Tip

只在记录日志时使用流.

定义:

流用来替代 printf()scanf().

优点:

有了流, 在打印时不需要关心对象的类型. 不用担心格式化字符串与参数列表不匹配 (虽然在 gcc 中使用 printf 也不存在这个问题). 流的构造和析构函数会自动打开和关闭对应的文件.

缺点:

流使得 pread() 等功能函数很难执行. 如果不使用 printf 风格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s) 用流处理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而这一点对于软件国际化很有用.

结论:

不要使用流, 除非是日志接口需要. 使用 printf 之类的代替.使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流.

拓展讨论:

对这一条规则存在一些争论, 这儿给出点深层次原因. 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致. 因此, 我们不希望用户来决定是使用流还是 printf + read/write. 相反, 我们应该决定到底用哪一种方式. 把日志作为特例是因为日志是一个非常独特的应用, 还有一些是历史原因.流的支持者们主张流是不二之选, 但观点并不是那么清晰有力. 他们指出的流的每个优势也都是其劣势. 流最大的优势是在输出时不需要关心打印对象的类型. 这是一个亮点. 同时, 也是一个不足: 你很容易用错类型, 而编译器不会报警. 使用流时容易造成的这类错误:


  1. cout << this; // 输出地址
    cout << *this; // 输出值


由于 << 被重载, 编译器不会报错. 就因为这一点我们反对使用操作符重载.有人说 printf 的格式化丑陋不堪, 易读性差, 但流也好不到哪儿去. 看看下面两段代码吧, 实现相同的功能, 哪个更清晰?


  1. cerr << "Error connecting to '" << foo->bar()->hostname.first
    << ":" << foo->bar()->hostname.second << ": " << strerror(errno);

    fprintf(stderr, "Error connecting to '%s:%u: %s",
    foo->bar()->hostname.first, foo->bar()->hostname.second,
    strerror(errno));


你可能会说, “把流封装一下就会比较好了”, 这儿可以, 其他地方呢? 而且不要忘了, 我们的目标是使语言更紧凑, 而不是添加一些别人需要学习的新装备.每一种方式都是各有利弊, “没有最好, 只有更适合”. 简单性原则告诫我们必须从中选择其一, 最后大多数决定采用 printf + read/write.