【问题标题】:Distinguishing between failure and end of file in read loop在读取循环中区分失败和文件结束
【发布时间】:2011-11-11 22:35:13
【问题描述】:

从 istream 中读取的惯用循环是

while (thestream >> value)
{
  // do something with value
}

现在这个循环有一个问题:它无法区分循环是由于文件结尾还是由于错误而终止。比如下面的测试程序:

#include <iostream>
#include <sstream>

void readbools(std::istream& is)
{
  bool b;
  while (is >> b)
  {
    std::cout << (b ? "T" : "F");
  }
  std::cout << " - " << is.good() << is.eof() << is.fail() << is.bad() << "\n";
}

void testread(std::string s)
{
  std::istringstream is(s);
  is >> std::boolalpha;
  readbools(is);
}

int main()
{
  testread("true false");
  testread("true false tr");
}

testread 的第一次调用包含两个有效的布尔值,因此不是错误。第二次调用以第三个不完整的布尔值结束,因此是一个错误。然而,两者的行为是相同的。在第一种情况下,读取布尔值失败是因为没有,而在第二种情况下它失败是因为它不完整,并且在两种情况下都命中了 EOF。确实,上面的程序输出了两次相同的行:

TF - 0110
TF - 0110

为了解决这个问题,我想到了以下解决方案:

while (thestream >> std::ws && !thestream.eof() && thestream >> value)
{
  // do something with value
}

这个想法是在实际尝试提取值之前检测常规 EOF。因为文件末尾可能有空格(这不会是错误,但会导致读取最后一项没有命中 EOF),我首先丢弃所有空格(不会失败),然后测试 EOF。只有当我不在文件末尾时,我才会尝试读取该值。

对于我的示例程序,它似乎确实有效,我得到了

TF - 0100
TF - 0110

所以在第一种情况下(正确输入),fail() 返回 false。

现在我的问题是:这个解决方案是否保证有效,或者我只是(不)幸运地碰巧给出了预期的结果?另外:是否有更简单的(或者,如果我的解决方案是错误的,是正确的)方法来获得所需的结果?

【问题讨论】:

  • 想要的结果是什么?还要检查文件是否有效?你在这两种情况下都有相同的结果......
  • @neagoegab:期望的结果是检测循环是否由于到达文件末尾或由于输入错误而终止。至少在我的实验中,结果相同,请参见四位数块的第三位:对于非错误情况,它读​​取 0100,对于错误情况,它读​​取 0110。因为第三位是fail()的值,表示至少对于本次测试,fail()可以区分这两种情况。
  • 那你的答案是正确的。另请注意,您正在同时验证和处理流。如果这对您来说不是问题,那很好......
  • @neagoegab:惯用循环还同时验证和处理流。
  • 我认为惯用循环假设流中的数据是“有效的”......对于示例应用程序来说,无论是什么有效手段。您的第二个字符串不是有效的输入。

标签: c++ error-handling istream


【解决方案1】:

只要不将流配置为使用异常,就很容易区分 EOF 和其他错误。

只需检查最后的stream.eof()

在此之前只检查失败/非失败,例如stream.fail()!stream。注意good 不是fail 的反义词。所以一般来说,从不看good,只看fail


编辑:

一些示例代码,即修改您的示例以区分数据中的不良 bool 规范:

#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>
using namespace std;

bool throwX( string const& s )  { throw runtime_error( s ); }
bool hopefully( bool v )        { return v; }

bool boolFrom( string const& s )
{
    istringstream stream( s );
    (stream >> boolalpha)
        || throwX( "boolFrom: failed to set boolalpha mode." );

    bool result;
    (stream >> result)
        || throwX( "boolFrom: failed to extract 'bool' value." );
        
    char c;  stream >> c;
    hopefully( stream.eof() )
        || throwX( "boolFrom: found extra characters at end." );
    
    return result;
}

void readbools( istream& is )
{
    string word;
    while( is >> word )
    {
        try
        {
            bool const b = boolFrom( word );
            cout << (b ? "T" : "F") << endl;
        }
        catch( exception const& x )
        {
            cerr << "!" << x.what() << endl;
        }
    }
    cout << "- " << is.good() << is.eof() << is.fail() << is.bad() << "\n";
}

void testread( string const& s )
{
    istringstream is( s );
    readbools( is );
}

int main()
{
  cout << string( 60, '-' ) << endl;
  testread( "true false" );

  cout << string( 60, '-' ) << endl;
  testread( "true false tr" );

  cout << string( 60, '-' ) << endl;
  testread( "true false truex" );
}

示例结果:

-------------------------------------------------- ---------- 吨 F - 0110 -------------------------------------------------- ---------- 吨 F !boolFrom: 未能提取 'bool' 值。 - 0110 -------------------------------------------------- ---------- 吨 F !boolFrom: 在末尾发现多余的字符。 - 0110

编辑2:在贴出的代码和结果中,添加了使用eof()检查的例子,我忘记了。


编辑 3: 以下相应示例使用了 OP 提出的 skip-whitespace-before-reading 解决方案:

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

void readbools( istream& is )
{
    bool b;
    while( is >> ws && !is.eof() && is >> b )       // <- Proposed scheme.
    {
        cout << (b ? "T" : "F") << endl;
    }
    if( is.fail() )
    {
        cerr << "!readbools: failed to extract 'bool' value." << endl;
    }
    cout << "- " << is.good() << is.eof() << is.fail() << is.bad() << "\n";
}

void testread( string const& s )
{
    istringstream is( s );
    is >> boolalpha;
    readbools( is );
}

int main()
{
  cout << string( 60, '-' ) << endl;
  testread( "true false" );

  cout << string( 60, '-' ) << endl;
  testread( "true false tr" );

  cout << string( 60, '-' ) << endl;
  testread( "true false truex" );
}

示例结果:

-------------------------------------------------- ---------- 吨 F - 0100 -------------------------------------------------- ---------- 吨 F !readbools: 未能提取 'bool' 值。 - 0110 -------------------------------------------------- ---------- 吨 F 吨 !readbools: 未能提取 'bool' 值。 - 0010

主要区别在于,这种方法在第三种情况下会产生 3 个成功读取的值,即使第三个值指定不正确(如 "truex")。

即它无法识别不正确的规范。

当然,我编写 Code That Does Not Work™ 的能力并不能证明它不能工作。但是我非常擅长编写代码,并且我看不到任何方法可以使用这种方法来检测"truex" 不正确(虽然使用基于读取字异常的方法很容易做到)。所以至少对我来说,基于读取词异常的方法更简单,因为它很容易使其行为正确。

【讨论】:

  • 这还不够,因为在解析错误条目时可能会遇到 EOF。这正是我的示例代码演示的内容:正确和错误的输入都同时设置了 eof 和 fail,因为错误位于文件末尾。
  • @AlfPSteinbach:再看一下我的示例程序的输出:对于正确和错误的字符串,最终的位模式都是 0110,这意味着 eof()fail() 都返回 true。那是因为eof() 告诉你的只是到达文件末尾,而不是到达文件末尾的最后一次读取是否成功。
  • @celtschk:足够了。我添加了一些可能对你有帮助的代码。
  • @celtshk:重新检查 eof(),在上面的代码中是 boolFrom 函数。你最好就你提出的新问题提出一个新问题。例如,您提出解析器函数或流是否规定输入格式的问题。你的问题没有提到这个问题。如果您有兴趣,请提出一个新的 SO 问题。或者如果您有改进 iostream 的想法,比如您不太喜欢这里给出的答案,那么您可以发布到 [comp.std.c++]。
  • @ApfPSteinbach:所以现在有了这个补充,你的答案基本上变成了:我的版本正确,你的解决方案更容易正确(而不是以前:您的解决方案更简单)。所以这个答案我可以接受。谢谢。
猜你喜欢
  • 1970-01-01
  • 2013-09-16
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多