为什么在读取eof时设置failbit?

问题描述 投票:19回答:3

我读过<fstream>早于<exception>。忽略fstream上的异常信息量不大的事实,我有以下问题:

可以使用exceptions()方法在文件流上启用异常。

ifstream stream;
stream.exceptions(ifstream::failbit | ifstream::badbit);
stream.open(filename.c_str(), ios::binary);

任何尝试打开不存在的文件,没有正确权限的文件或任何其他I / O问题都将导致异常。使用自信的编程风格非常好。该文件应该在那里并且可读。如果条件不满足,我们会得到例外。如果我不确定文件是否可以安全打开,我可以使用其他功能来测试它。

但现在假设我尝试读入缓冲区,如下所示:

char buffer[10];
stream.read(buffer, sizeof(buffer)); 

如果流在填充缓冲区之前检测到文件结束,则流决定设置failbit,如果启用了异常,则会触发异常。为什么?这有什么意义?我本可以证实在阅读后只测试eof()

char buffer[10];
stream.read(buffer, sizeof(buffer));
if (stream.eof()) // or stream.gcount() != sizeof(buffer)
    // handle eof myself

这种设计选择使我无法在流上使用标准异常,并迫使我在权限或I / O错误上创建自己的异常处理。或者我错过了什么?有什么出路吗?例如,我可以轻松测试是否可以在流之前读取流上的sizeof(buffer)字节吗?

c++ fstream eof
3个回答
23
投票

failbit旨在允许流报告某些操作未能成功完成。这包括诸如无法打开文件,尝试读取不存在的数据以及尝试读取错误类型的数据等错误。

你要问的具体案例在这里重印:

char buffer[10];
stream.read(buffer, sizeof(buffer)); 

您的问题是为什么在读取所有输入之前达到文件结尾时设置failbit。原因是这意味着读取操作失败 - 您要求读取10个字符,但文件中没有足够多的字符。因此,操作未成功完成,并且流信号failbit会让您知道这一点,即使将读取可用字符。

如果要执行读取操作,您希望读取多个字符,可以使用readsome成员函数:

char buffer[10];
streamsize numRead = stream.readsome(buffer, sizeof(buffer)); 

此函数将读取文件末尾的字符,但与read不同,如果在读取字符之前到达文件末尾,则不会设置failbit。换句话说,它说“尝试阅读这么多字符,但如果你不能,那就不是错误。只要让我知道你读了多少。”这与read形成鲜明对比,137 说:“我想要这么多字符,如果你不能这样做,那就错了。”

编辑:我忘了提到的一个重要细节是可以设置eofbit而不触发failbit。例如,假设我有一个包含文本的文本文件

ifstream input("myfile.txt");

int value;
input >> value;

之后没有任何换行符或尾随空格。如果我写这段代码:

input.eof()

然后在这一点上input.fail()将返回true,因为当从文件中读取字符时,流命中文件的末尾,试图查看流中是否还有其他字符。但是,char buffer[10]; streamsize num_read = stream.rdbuf()->sgetn(buffer, sizeof(buffer)); 不会返回true,因为操作成功 - 我们确实可以读取文件中的整数。

希望这可以帮助!


2
投票

直接使用底层缓冲区似乎可以解决问题:

readeof()

1
投票

改进@缺席的答案,它遵循一个方法read()做同样的#include <iostream> #include <fstream> #include <stdexcept> using namespace std; streamsize readeof(istream &stream, char *buffer, streamsize count) { streamsize offset = 0; streamsize reads; while (!stream.eof()) { // Check also for already failed streams if (stream.fail()) throw runtime_error("Stream I/O error while reading"); if (count == 0) return offset; // This consistently fails on gcc (linux) 4.8.1 with failbit set on read // failure. This apparently never fails on VS2010 and VS2013 (Windows 7) reads = stream.rdbuf()->sgetn(buffer + offset, count); // This rarely sets failbit on VS2010 and VS2013 (Windows 7) on read // failure of the previous sgetn() (void)stream.rdstate(); // On gcc (linux) 4.8.1 and VS2010/VS2013 (Windows 7) this consistently // sets eofbit when stream is EOF for the conseguences of sgetn(). It // should also throw if exceptions are set, or return on the contrary, // and previous rdstate() restored a failbit on Windows. On Windows most // of the times it sets eofbit even on real read failure stream.peek(); offset += reads; count -= reads; } return offset; } #define BIGGER_BUFFER_SIZE 200000000 int main(int argc, char* argv[]) { ifstream stream; stream.exceptions(ifstream::badbit | ifstream::failbit); stream.open("<big file on usb stick>", ios::binary); char *buffer = new char[BIGGER_BUFFER_SIZE]; streamsize reads = readeof(stream, buffer, BIGGER_BUFFER_SIZE); if (stream.eof()) cout << "eof" << endl << flush; delete buffer; return 0; } 但没有设置EOF的failbit。此外,还测试了真正的读取故障,例如通过硬件移除USB记忆棒或网络共享访问中的链接丢失导致的中断传输。它已在Windows 7上使用VS2010和VS2013进行了测试,在Linux上使用gcc 4.8.1进行了测试。在linux上只尝试了USB棒的移除。

sgetn()

底线:在linux上,行为更加一致和有意义。如果在实际读取失败时启用了异常,则会抛出qazxswpoi。相反,Windows会在大多数情况下将读取失败视为EOF。

© www.soinside.com 2019 - 2024. All rights reserved.