4.3 处理流错误

前面涉及的Date提取符在某种情况下将设置流的失败位。那么,用户如何知道这样的失败是何时发生的呢?可以通过调用流的成员函数来测试流错误,或者如果不关心到底发生了什么错误,你可以只是在一个布尔环境中评估该流。这两种技术都依赖于流的错误位的状态。

1.流状态

类ios_base从类ios派生而来[1],定义了4个标志位来测试流的状态:

4.3 处理流错误 - 图1

可以通过调用相应成员函数,根据其返回的布尔值来测试发生了什么情况,返回的布尔值指出设置了哪个标志位。当除了goodbit之外的其他3个标志位没有被设置时,流类的成员函数good()返回真(true)。如果eofbit被设置则函数eof()返回真,表明程序试图从已经到达末尾的流(通常是文件流)中读取数据。因为输入结束(end-of-input)发生在C++的读操作试图越过物理介质末尾的时候,所以failbit标志位也会被设置,表示期望获取的数据没有被成功读取。如果设置了failbit或badbit标志位中的任意一个,则函数fail_()返回真,只有设置了badbit标志位,函数bad()才返回真。

一旦设置了流状态中的任何一个标志位,这些标志位将保持不变,但程序员并不希望总保持这种状况。读文件时,也许想在文件结束之前将文件指针重定位到前面的位置。仅靠移动文件指针不会自动重置eofbit或failbit标志位。需要程序员自己在程序中调用clear()函数来清空标志位,如下所示:

4.3 处理流错误 - 图2

调用clear()后,立即调用函数good()则返回true。正如较早时提及的Date提取符一样,函数setstate()用来设置标志位,而标志位的值由我们传递给它。函数setstate()不会影响其他标志位,如果已经设置了其他的标志位,则这些标志位将保持不变。如果想设置某个特定的标志位而重置其他所有的标志位,可以调用重载的clear()函数,将需要设置的标志位的表达式作为参数传递给它,如下所示:

4.3 处理流错误 - 图3

在大多数情况下,程序员不想逐个检查流状态位,只想知道所有操作是否成功完成。当从头到尾读文件时就是这种情况,此时只想知道什么时候读文件完成。可以使用返回值为void*的转换函数,当流出现在布尔表达式中时,这个函数被自动调用。使用下面的语句完成流的读入,直到输入结束:

4.3 处理流错误 - 图4

记住,operator>>()返回它的流参数,所以上面的while语句用布尔表达式对流进行测试。这个特殊的例子假定输入流myStream包含由空格符分隔的整数。函数ios_base:operator void*()仅在流上调用函数good()并返回调用结果。[2]因为大部分流操作返回流对象,所以使用这个语句是比较方便的。

2.流类和异常

在出现异常概念之前的很长时间里,输入输出流是作为C++的一部分存在的,所以一直采用手工方式检查流状态。为了保持向后兼容性,这种手工检查流状态的方法仍能在程序中使用,但是现代的输入输出流采用抛出异常的方法来代替它。流的成员函数exceptions()接受一个参数,这个参数用于表示程序员希望在哪个状态位出现时抛出异常。当遇到这样的状态时,就抛出一个std:ios_base:failure类型的异常,std:ios_base:failure继承自std:exception。

尽管可以为4种流状态中的任何一个状态触发失败异常,但是这样做并不是好办法。如第1章所述,要在真正发生异常的情况下使用异常,但是“已至文件尾”不仅不是异常,而且是在预料之中的正常情况。因此读者也许只想对badbit所代表的错误使用异常,使用方法如下:

4.3 处理流错误 - 图5

在流接流(stream-by-stream)的基础上能使用异常,因为exceptions()是流类的成员函数。函数exceptions()返回位掩码(bitmask)[3](iostate类型,它依赖于编译器,可转成int型),表示哪种流状态会触发异常。如果这些状态位已经设置,就会立即抛出异常。当然,如果与流一起使用异常,则最好捕获异常,这意味着需要把流处理过程封装到含有ios:failure处理器的try块中。许多程序员认为这很乏味,他们只在发生错误的地方手工检查错误状态(因此假如,大部分情况下他们不希望bad()返回true)。这是让流抛出异常成为可选项而不是默认项的另一原因。在任何情况下都可以选择处理流错误的方式。基于相同的原因,我们推荐使用异常进行错误处理,这里也采用这种方法。

[1]由于这个原因,可以使用ios:failbit取代ios_base:failbit以节省输入。

[2]习惯上优先使用函数operator void()而不是operator bool(),因为从bool型隐式转换到int型会引起错误,在用整形表达式时,不应该错误地应用流。函数operator void()在布尔表达式中应该隐式调用。

[3]用作单个标志位的一个整数类型。