Различение сбоя и конца файла в цикле чтения

#c #error-handling #istream

#c #обработка ошибок #istream

Вопрос:

Идиоматический цикл для чтения из istream — это

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

Теперь у этого цикла есть одна проблема: он не будет различать, завершился ли цикл из-за конца файла или из-за ошибки. Для примера возьмем следующую тестовую программу:

 #include <iostream>
#include <sstream>

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

 TF - 0110
TF - 0110
  

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

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

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

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

 TF - 0100
TF - 0110
  

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

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

Комментарии:

1. каков желаемый результат? также проверить, является ли файл допустимым? в обоих случаях вы получаете одинаковый результат…

2. @neagoegab: желаемый результат — определить, был ли цикл завершен только из-за достижения конца файла или из-за ошибочной записи. И, по крайней мере, в моем эксперименте результаты не совпадают, смотрите третью цифру четырехзначного блока: в случае отсутствия ошибки он читает 0100, в случае ошибки он читает 0110. Поскольку третий бит является значением fail() , это означает, что, по крайней мере, для этого теста, fail() можно различать оба случая.

3. Тогда ваш ответ правильный. Также, пожалуйста, обратите внимание, что вы проверяете и обрабатываете поток одновременно. Если для вас это не проблема, то все в порядке…

4. @neagoegab: идиоматический цикл также проверяет и обрабатывает поток одновременно.

5. Я думаю, что идиоматический цикл предполагает, что данные в потоке «действительны»… что действительно означает для примера приложения. Ваша вторая строка не является допустимым вводом.

Ответ №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 constamp; s )  { throw runtime_error( s ); }
bool hopefully( bool v )        { return v; }

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

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

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

void testread( string constamp; 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( istreamamp; is )
{
    bool b;
    while( is >> ws amp;amp; !is.eof() amp;amp; 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 constamp; 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

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

Т.е. ему не удается распознать неправильную спецификацию как таковую.

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

Комментарии:

1. Этого недостаточно, потому что EOF может быть сбит при разборе ошибочной записи. Это именно то, что демонстрирует мой пример кода: как для правильного, так и для неправильного ввода одновременно установлены eof и fail, потому что ошибка была в конце файла.

2. @AlfPSteinbach: Посмотрите еще раз на выходные данные моего примера программы: как для правильной, так и для ошибочной строки конечным битовым шаблоном было 0110, что означает, что оба eof() и fail() вернули значение true. Это потому, что все eof() говорит вам о том, что был достигнут конец файла, а не о том, было ли успешным последнее чтение, которое достигло конца файла.

3. @celtschk: этого достаточно. я добавил некоторый код, который может вам помочь.

4. @celtshk: просто проверяем eof(), в приведенном выше коде это находится в boolFrom функции. вам лучше задать новый вопрос о новых проблемах, которые вы поднимаете. например, вы поднимаете вопрос о том, функция синтаксического анализа или поток диктует формат ввода. в вашем вопросе эта проблема нигде не затрагивалась. если вам интересно, задайте новый вопрос SO. или, если у вас есть идеи по улучшению iostreams, например, вам не совсем понравился приведенный здесь ответ, тогда вы можете опубликовать на [comp.std.c ].

5. @ApfPSteinbach: Итак, теперь, с этим дополнением, ваш ответ в основном изменился на: моя версия не верна, и ваше решение легче получить правильно (в отличие от предыдущего: ваше решение проще). Так что этот ответ я могу принять. Спасибо.