Различение между ошибкой и концом файла в цикле чтения - PullRequest
11 голосов
/ 12 ноября 2011

Идиоматический цикл для чтения из 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 содержит два допустимых значения bool и поэтому не является ошибкой.Второй вызов заканчивается третьим, неполным bool, и поэтому является ошибкой.Тем не менее, поведение обоих одинаково.В первом случае чтение логического значения завершается неудачно, потому что его нет, а во втором случае происходит сбой, потому что оно неполное, и в обоих случаях достигается EOF.Действительно, вышеприведенная программа дважды выводит одну и ту же строку:

TF - 0110
TF - 0110

Чтобы решить эту проблему, я подумал о следующем решении:

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

Идея состоит в том, чтобы обнаружить регулярные EOF до того, какпытаясь извлечь значение.Поскольку в конце файла могут быть пробелы (что не будет ошибкой, но приведет к тому, что чтение последнего элемента не достигнет EOF), я сначала отбрасываю все пробелы (которые не могут завершиться ошибкой), а затем проверяю на EOF.Только если я не в конце файла, я пытаюсь прочитать значение.

Для моего примера программы оно действительно работает, и я получаю

TF - 0100
TF - 0110

Так что вв первом случае (правильный ввод), * ​​1019 * возвращает false.

Теперь мой вопрос: гарантированно ли работает это решение, или мне просто (не) повезло, что оно дало желаемый результат?Также: Есть ли более простой (или, если моё решение неверное, правильное) способ получить желаемый результат?

1 Ответ

7 голосов
/ 12 ноября 2011

Очень легко различить EOF и другие ошибки, если вы не сконфигурируете поток для использования исключений.

Просто отметьте stream.eof() в конце.

Перед этим проверяйте только неисправность / неисправность, например, stream.fail() или !stream. Обратите внимание, что good не является противоположностью fail. Так что вообще никогда даже не смотрите на good, только на fail.


Edit:

Некоторый пример кода, а именно ваш пример, изменен, чтобы различать плохую спецификацию 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" );
}

Пример результата:

------------------------------------------------------------
T
F
- 0110
------------------------------------------------------------
T
F
!boolFrom: failed to extract 'bool' value.
- 0110
------------------------------------------------------------
T
F
!boolFrom: found extra characters at end.
- 0110

Редактировать 2 : в опубликованном коде и результатах добавлен пример использования eof() проверки, которую я забыл.


Редактировать 3 : В следующем соответствующем примере используется предложенное OP решение skip-whitespace-before-read:

#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: failed to extract 'bool' value.
- 0110
------------------------------------------------------------
T
F
T
!readbools: failed to extract 'bool' value.
- 0010

Основное отличие состоит в том, что в третьем случае этот подход дает 3 успешно прочитанных значения, даже если третье значение указано неправильно (как "truex").

т.е. он не может распознать неверную спецификацию как таковую.

Конечно, моя способность писать код, который не работает и торгует; нет никаких доказательств того, что это не может работать. Но я довольно хорошо разбираюсь в вещах и не вижу способа обнаружить "truex" как неправильный с этим подходом (хотя это было легко сделать с помощью подхода, основанного на исключениях из чтения слов). Так что, по крайней мере, для меня подход, основанный на исключениях из чтения слов, проще в том смысле, что легко заставить его вести себя правильно.

Приветствия и hth.,

...