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

cez*_*tko 20 c++ fstream eof

我读过那个<fstream>早期的<exception>.忽略了例外fstream情况不是很有用的事实,我有以下问题:

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

ifstream stream;
stream.exceptions(ifstream::failbit | ifstream::badbit);
stream.open(filename.c_str(), ios::binary);
Run Code Online (Sandbox Code Playgroud)

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

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

char buffer[10];
stream.read(buffer, sizeof(buffer)); 
Run Code Online (Sandbox Code Playgroud)

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

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

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

tem*_*def 23

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

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

char buffer[10];
stream.read(buffer, sizeof(buffer)); 
Run Code Online (Sandbox Code Playgroud)

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

如果你想要做的,你想读的读操作一定数目的字符,你可以使用readsome成员函数:

char buffer[10];
streamsize numRead = stream.readsome(buffer, sizeof(buffer)); 
Run Code Online (Sandbox Code Playgroud)

此函数将读取文件末尾的字符,但与读取字符之前到达文件末尾时read不设置failbit 不同.换句话说,它说"尝试阅读这么多字符,但如果你不能,那就不是错误.只要让我知道你读了多少." 与此相反read,它说:"我想正是这样多的字符,如果你不能做到这一点这是一个错误."

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

137
Run Code Online (Sandbox Code Playgroud)

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

ifstream input("myfile.txt");

int value;
input >> value;
Run Code Online (Sandbox Code Playgroud)

然后此时input.eof()将返回true,因为当从文件中读取字符时,流命中文件的末尾,试图查看流中是否还有其他字符.然而,input.fail()返回true,因为操作成功-我们的确可以从文件中读取一个整数.

希望这可以帮助!

  • 知道了 抱歉-我误读了规范中对“ readsome”的描述。没错,`readsome`只会读取已经缓冲的内容,而不必让流缓冲区从设备中提取更多数据。 (2认同)

cez*_*tko 1

改进@absence的答案,它遵循一种readeof()执行相同操作read()但不在EOF上设置故障位的方法。还测试了真实的读取失败,例如硬拔除 USB 记忆棒或网络共享访问中的链接丢失导致的传输中断。它已在使用 VS2010 和 VS2013 的 Windows 7 以及使用 gcc 4.8.1 的 Linux 上进行了测试。在 Linux 上仅尝试过 USB 棒移除。

#include <iostream>
#include <fstream>
#include <stdexcept>

using namespace std;

streamsize readeof(istream &stream, char *buffer, streamsize count)
{
    if (count == 0 || stream.eof())
        return 0;

    streamsize offset = 0;
    streamsize reads;
    do
    {
        // 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
        (void)stream.peek();

        if (stream.fail())
            throw runtime_error("Stream I/O error while reading");

        offset += reads;
        count -= reads;
    } while (count != 0 && !stream.eof());

    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;
}
Run Code Online (Sandbox Code Playgroud)

底线:在 Linux 上,行为更加一致和有意义。如果在实际读取失败时启用异常,它将引发sgetn(). 相反,Windows 大多数时候会将读取失败视为 EOF。