31

В разных источниках говорят что использование std::istream::eof() - это признак плохого кода и что в частности неправильно писать:

while (!input_stream.eof()) {
    input_stream >> value;
    process(value);
}

Что в этом коде не так? Как писать правильно?

Abyx
  • 31,143

3 Answers3

30

Проблема std::istream::eof() в том, что он выставляется только после выполнения какой-либо операции чтения. Поэтому происходит следующее:

std::ifstream input_stream("empty_file.txt"); // Открываем пустой файл
if (!input_stream.eof()) { // eof() == false, т.к. мы еще ничего не читали
    int value;
    input_stream >> value; // Пытаемся читать число, а его там нет.
    // Здесь eof() == true, но мы это не проверяем.
    std::cout << value; // Выведется 0.
}

Так что если и вызывать eof(), то это надо делать после операции чтения.

Однако, объекты std::istream умеют преобразовываться в bool, а каждая операция чтения возвращает ссылку на std::istream. Поэтому идиоматичный код выглядит следующим образом:

while (input_stream >> value) {
    process(value);
}

Или для строки:

std::string str;
while (std::getline(input_stream, str)) { ... }

Или сразу для нескольких значений:

while (input_stream >> value1 >> value2) { ... }

Здесь если при чтении value1 произойдет ошибка, то все последующие операции чтения (т.е. value2) будут игнорироваться, по этому можно читать сразу несколько значений сразу, и уже потом проверять состояние std::istream.

Arhadthedev
  • 11,528
Abyx
  • 31,143
  • 1
    Угу. Это одна из тех вещей, где семантику принесли в жертву эффективности. Правильное название для eof было бы last_operation_failed_with_eof, что на самом деле ужасно. Я бы сказал, это ошибка в стандартной библиотеке. – VladD May 05 '15 at 18:29
  • // Здесь eof() == false, но мы это не проверяем. - имеется в виду true? – Qwertiy May 05 '15 at 20:17
  • 1
    @Abyx, у тебя таки Q-A тем тут дофига, не думал недостающее задвинуть в C++ FAQ (который: https://isocpp.org/faq)? :-) – Monah Tuk Mar 17 '16 at 02:12
  • @VladD не соглашусь. Если хоть чуть-чуть подумать над задачей определения конца файла, то становится очевидно, что в огромном числе случаев его можно достигнуть только при неудачной попытке чтения! Достаточно лишнего пробела или перевода строки в конце файла... Так что выбор API тут оправдан. – Pavel Mayorov Jan 09 '19 at 06:33
  • @VladD что же до семантики - можно представить EOF как "виртуальный" символ, записанный в конце каждого файла. – Pavel Mayorov Jan 09 '19 at 06:34
  • @PavelMayorov: Вы имеете в виду, stream'а? Потому что конец файла-то проверить просто: текущая позиция равна длине минус 1. Я, собственно, прекрасно понимаю осмысленность такого API. Я против имени, оно подобрано крайне неудачно и не отображает адекватно семантику API. (Доказательство этого — количество вопросов от новичков и необходимость держать в голове «правильные» паттерны.) – VladD Jan 13 '19 at 13:09
  • @PavelMayorov: Виртуальный EOF не спасает, это протекающая абстракция. Он не достигается при помощи seek, и невозможно прочитать «за» него. Ну и запомнить, что «конец файла» — это на самом деле «виртуальный символ, который называется EOF, не читается и отбрасывает позицию после чтения на 1 назад», не выглядит хорошим решением. Правильное API с моей точки зрения было бы поле stream_errors, в котором присутствовал бы нужный флаг. – VladD Jan 13 '19 at 13:14
  • @VladD знание длины файла не поможет: в конце может быть любое число пробельных символов, из-за которых конец вроде как еще не достигнут, но очередное число прочитать уже нельзя. – Pavel Mayorov Jan 13 '19 at 17:33
  • @PavelMayorov: Ну, гарантии успешного чтения вообще ничего дать не может. Если вы читаете число, то в файле вместо чисел может быть что угодно. Я, собственно, ругаюсь лишь на название. – VladD Jan 13 '19 at 21:32
  • Вопос в том, что считать eof'ом. Если это «мы находимся в физическом конце файла», то это сравнение позиции и длины. А если это «операция чтения следующего числа/whatever будет успешной», то это в принципе невозможно. Ну а «предыдущая операция была неуспешна» — эт ото, что мне не нравится. – VladD Jan 13 '19 at 21:40
  • Дополню, что "Выведется 0", т.к. это явно прописано в спецификации operator >> у std::basic_istream. Если бы >> не менял значение аргумента при ошибке, то при его чтении в std::cout << value; было бы UB. – tocic Oct 22 '22 at 04:49
2

eof() - это всего лишь функция, которая возвращает true, если обнаружен конец файла. А если кто-то ей пользуется после того, как прочитал мусор куда-то, где он не нужен - вот это уже, возможно, плохой код. Например:

char value;
if (!(input_stream.get(value))) // если поток в ошибочном состоянии
{
    if (input_stream.eof()) // стал ли причиной конец файла
    {    

ничего плохого тут не вижу

bugaboo
  • 29
  • И зачем ты это написал?EOF - это константа, определенная в stdio.h, значение которой может быть присвоено вместо значения из файла, когда читают в int. – bugaboo Jun 17 '17 at 11:08
  • Я просто мимо проходил и ясно выразил свое мнение с примером. Совсем не для того, чтобы ко мне придирались. – bugaboo Jun 17 '17 at 11:35
  • 1
    Вы не просто высказываете мнение, вы публикуете код, который увидят другие и захотят воспользоваться. Или будут по нему учиться. Поэтому да, вы должны рассчитывать на то, что могут быть критические комментарии. Критические комментарии помогают читателям понять, хороший код или не очень. – VladD Jun 17 '17 at 13:32
  • А что не так с моим кодом? Вопрос стоит так, что eof() это плохо, я написал, что плохо - это когда им неправильно пользуются, привел пример рабочего кода. Увидев малейшую описку в тексте сообщения автор темы фиксирует своё и моё внимание на ней, а не на сути того, что я написал. А заодно и ваше. Давайте поговорим об этом, ведь тут всем плевать на функцию eof(). – bugaboo Jun 17 '17 at 14:26
  • которая не имеет к константе EOF никакого отношения, а просто информирует, установлен ли флаг, и с тем же основанием может считаться плохой или хорошей, что и, например, функции bad(), fail(), rdstate() и т.д. – bugaboo Jun 17 '17 at 14:52
-2

Всем, кто оставил здесь ответы и комментарии, советую "зреть в корень"!!! Cмотрите библу и не забывайте для какого языка вы ищете информацию для си или с++.

По вопросу: считаю что ничего зазорного нет. Надуманно и не обосновано. Ниже привел, то что выводит ФУНКЦИЯ(в данном контексте) eof(), а также комментарий к функции. Это лежит в файле basic_ios.h:

Относится к функции:

   *  @brief  Fast error checking.
   *  @return  True if the eofbit is set.
   *  Note that other iostate flags may also be set.

Относится к значению, которое возвращается:

/// Indicates that an input operation reached the end of an input sequence. static const iostate eofbit = _S_eofbit;

То бишь это функция которая возвращает true при достижении конца ввода (файла при чтении). Всё зависит от того, какой код вы пишите, с точки зрения логики и правильности ветвлений.

Art_K
  • 1