为什么 stream::good 是错误的示例?

Example of Why stream::good is Wrong?

我给出了一个答案,我想通过循环每次检查流的有效性here

我的原始代码使用 good 并且看起来类似于:

ifstream foo("foo.txt");

while (foo.good()){
    string bar;
    getline(foo, bar);
    cout << bar << endl;
}

我立即被指示 here 并告诉我永远不要测试 good。显然这是我不明白的事情,但我想正确地做我的文件 I/O。

我用几个示例测试了我的代码,无法使 good-测试代码失败。

首先(打印正确,以新行结尾):

bleck 1
blee 1 2
blah
ends in new line

第二个(打印正确,以最后一行结尾):

bleck 1
blee 1 2
blah
this doesn't end in a new line

第三个是一个空文件(打印正确,一个换行符。)

第四个是丢失的文件(这正确打印了任何内容。)

谁能帮我举个例子来说明为什么 good-不应该进行测试?

他们错了。口头禅是'never test .eof()'.

  • Why is iostream::eof inside a loop condition considered wrong?

即使是这个咒语也有点过分了,因为两者都有助于在提取失败后诊断流的状态。

所以口头禅应该更像

Don't use good() or eof() to detect eof before you try to read any further

fail()bad()

相同

当然 stream.good 可以在使用流之前有用地使用(例如,如果流是尚未成功打开的文件流)

然而,两者都非常非常经常地滥用来检测输入的结束,而这不是它的工作原理。


不应该使用此方法的典型示例:

std::istringstream stream("a");
char ch;
if (stream >> ch) {
   std::cout << "At eof? " << std::boolalpha << stream.eof() << "\n";
   std::cout << "good? " << std::boolalpha << stream.good() << "\n";
}

版画

false
true

看到了Live On Coliru

good()eof() 都会在代码中多出一行。如果你有一个空白文件并且 运行 这个:

std::ifstream foo1("foo1.txt");
std::string line;
int lineNum = 1;

std::cout << "foo1.txt Controlled With good():\n";
while (foo1.good())
{
    std::getline(foo1, line);
    std::cout << lineNum++ << line << std::endl;
}
foo1.close();
foo1.open("foo1.txt");
lineNum = 1;

std::cout << "\n\nfoo1.txt Controlled With getline():\n";
while (std::getline(foo1, line))
{
    std::cout << line << std::endl;
}

您将获得的输出是

foo1.txt Controlled With good():
1

foo1.txt Controlled With getline():

这证明它无法正常工作,因为永远不应读取空白文件。知道这一点的唯一方法是使用读取条件,因为流在​​第一次读取时总是好的。

使用 foo.good() 只是告诉您上一个读取操作工作得很好,下一个也可能工作。 .good() 检查流在给定点的状态。它不检查是否到达文件末尾。假设在读取文件时发生了一些事情(网络错误,os 错误,...) 好事将失败。这并不意味着已到达文件末尾。然而,当到达文件末尾时 .good() 失败,因为流无法再读取。

另一方面,.eof() 检查是否确实到达了文件末尾。

因此,.good() 可能会在未到达文件末尾时失败。

希望这可以帮助您理解为什么使用 .good() 检查文件末尾是一个坏习惯。

这已在其他答案中涵盖,但为了完整起见,我将简要介绍一下。与

唯一的功能区别
while(foo.good()) { // effectively same as while(foo) {
    getline(foo, bar);
    consume(bar); // consume() represents any operation that uses bar
}

while(getline(foo, bar)){
    consume(bar);
}

是前者会在文件中没有行的情况下进行额外的循环,使得这种情况与一个空行的情况无法区分。我认为这不是通常 期望的行为。但我想这是见仁见智的事情。

正如 sehe 所说,口头禅是 overboard。这是一个简化。真正的重点是你不能 consume() 在测试失败之前读取流的结果或者至少是 EOF(并且在读取 之前的任何测试 都是无关紧要的) .这是人们在循环条件下测试 good() 时很容易做的事情。

但是,关于 getline() 的事情是它在内部测试 EOF,对于您和 returns 一个空字符串,即使只读取 EOF。因此,以前的版本可能大致类似于以下伪c++:

while(foo.good()) {
    // inside getline
    bar = "";               // Reset bar to empty
    string sentry;
    if(read_until_newline(foo, sentry)) {
        // The streams state is tested implicitly inside getline
        // after the value is read. Good
        bar = sentry        // The read value is used only if it's valid.
    // ...                  // Otherwise, bar is empty.
    consume(bar);
}

我希望这能说明我想表达的意思。可以说在 getline() 中有一个 "correct" 版本的读取循环 。这就是为什么使用 readline 至少部分满足规则,即使 outer 循环不符合。

但是,对于其他阅读方式,打破规则的伤害更大。考虑:

while(foo.good()) {
    int bar;
    foo >> bar;
    consume(bar);
}

不仅您总是获得额外的迭代,而且该迭代中的bar未初始化!

所以,简而言之,while(foo.good()) 在您的情况下是可以的,因为 getline() 与某些其他读取函数不同,它在读取 EOF 位后使输出处于有效状态。并且因为您不关心甚至不希望文件为空时进行额外的迭代。

让我明确地说 是正确的。

但是 Nathan Oliver, Neil Kirk, and user2079303 提出的选项是使用 readline 作为循环条件而不是 good。为了后代需要解决。

我们将问题中的循环与以下循环进行比较:

string bar;

while (getline(foo, bar)){
    cout << bar << endl;
}

因为 getline returns istream 作为第一个参数传递,并且因为当 istream 被转换为 bool it returns !(fail() || bad()) ,并且由于读取 EOF 字符将设置 failbiteofbit 这使得 getline 成为有效的循环条件。

然而,当使用 getline 作为条件时,行为确实发生了变化,因为如果读取仅包含 EOF 字符的行,循环将退出,从而阻止输出该行。这不会出现在示例 2 和 4 中。但是示例 1:

bleck 1
blee 1 2
blah
ends in new line

使用 good 循环条件打印:

bleck 1
blee 1 2
blah
ends in new line

但是用 getline 循环条件截断最后一行:

bleck 1
blee 1 2
blah
ends in new line

示例 3 是一个空文件:

good 条件下打印:

getline 条件下不打印任何内容。

这些行为都没有错。 但是最后一行代码可能会有所不同。希望这个答案对您在出于编码目的在两者之间做出决定时有所帮助。