在读取循环中区分失败和文件结束

12

从 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。

现在我的问题是:这个解决方案是否保证可行,或者我只是(不)幸运地得到了所需的结果?此外:是否有一种更简单(或者,如果我的解决方案错误,则是正确的)方法来获得所需的结果?


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

8
只要不配置流使用异常,就很容易区分EOF和其他错误。
在结尾处简单地检查stream.eof()即可。
在此之前仅检查失败/非失败,例如stream.fail()! stream。请注意,good并非fail的相反。因此通常只应查看fail,而不是good
编辑: 一些示例代码,即修改后的示例以区分数据中未正确指定的布尔值:
#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" );
}

示例结果:

------------------------------------------------------------
T
F
- 0110
------------------------------------------------------------
T
F
!boolFrom:无法提取“bool”值。
- 0110
------------------------------------------------------------
T
F
!boolFrom:末尾找到额外字符。
- 0110

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


编辑3: 以下对应示例使用OP提出的读取前跳过空白解决方案:

#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" );
}

示例结果:

------------------------------------------------------------
T
F
- 0100
------------------------------------------------------------
T
F
!readbools:无法提取“bool”值。
- 0110
------------------------------------------------------------
T
F
T
!readbools:无法提取“bool”值。
- 0010

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

也就是说,它无法将错误的规范识别为错误。当然,我写的代码不能正常工作并不意味着它不能正常工作。但是我很擅长编写代码,并且我找不到任何一种方法可以使用此方法检测"truex"是否不正确(而基于读取单词的异常处理方法很容易实现)。因此,至少对我来说,基于读取单词的异常处理方法更简单,因为很容易使其正确运行。


这还不够,因为在解析错误条目时可能会遇到EOF。这正是我的示例代码所展示的:无论正确或错误的输入都会在文件末尾同时设置eof和fail标志,因为错误发生在文件结尾处。 - celtschk
@AlfPSteinbach:再看一下我的示例程序的输出:对于正确和错误的字符串,最终的位模式都是0110,这意味着eof()fail()都返回了true。这是因为eof()只告诉你文件已经结束,而不是最后一次读取是否成功,即使是读到了文件末尾。 - celtschk
@celtschk:这已经足够了。我添加了一些代码,可能会对你有所帮助。 - Cheers and hth. - Alf
1
@celtshk:只是在检查eof(),在上面的代码中是在boolFrom函数中。你最好就你提出的新问题提出一个新的问题。例如,你提出了解析器函数或流控制输入格式的问题。在你的问题中没有涉及到这个问题。如果你感兴趣,可以提出一个新的SO问题。或者如果你有改进iostreams的想法,比如你不太喜欢这里给出的答案,那么你可以发布到[comp.std.c++]。 - Cheers and hth. - Alf
1
@ApfPSteinbach: 所以现在加上这个,你的回答基本上变成了:我的版本正确,而你的解决方案更容易得到正确(与之前的相反:你的解决方案更简单)。所以我可以接受那个答案。谢谢。 - celtschk
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接