Всегда ли безопасно использовать std :: istream :: peek ()? - PullRequest
4 голосов
/ 21 февраля 2020

Я обычно учу своих учеников, что безопасный способ ввода файлов:

while (true) {
    // Try to read
    if (/* failure check */) {
        break;
    }
    // Use what you read
}

Это спасло меня и многих людей от классической и большую часть времени ошибалось:

while (!is.eof()) {
    // Try to read
    // Use what you read
}

Но людям действительно нравится эта форма зацикливания, поэтому стало привычным видеть это в коде ученика:

while (is.peek()!=EOF) { // <-- I know this is not C++ style, but this is how it is usually written
    // Try to read
    // Use what you read
}

Теперь вопрос: есть ли проблема с этим кодом? Есть ли случаи, когда все работает не так, как ожидалось? Хорошо, это два вопроса.

РЕДАКТИРОВАТЬ ДОПОЛНИТЕЛЬНЫЕ ДАННЫЕ: во время экзаменов вы иногда гарантируете учащимся, что файл будет правильно отформатирован, поэтому им не нужно делать все проверки и просто нужно проверить, есть ли еще данные. И большую часть времени мы имеем дело с двоичными форматами, которые позволяют вам вообще не беспокоиться о пробелах (потому что все данные значимы).

Хотя принятый ответ является совершенно ясным и правильным, я все же хотел бы, чтобы кто-то попытался прокомментировать совместное поведение peek() и unget().

Материал unget() пришёл мне в голову, потому что я однажды заметил (я думаю, что это было на Windows), что, посмотрев на ограничение внутреннего буфера 4096 (так эффективно вызывая загрузку нового буфера), не удалось получить предыдущий байт (последний из предыдущего буфера). Но я могу ошибаться. Так что это было мое дополнительное сомнение: что-то известное, что я пропустил, что, возможно, хорошо закодировано в стандарте или в некоторых реализациях библиотеки.

1 Ответ

8 голосов
/ 21 февраля 2020

is.peek()!=EOF сообщает вам, остались ли еще символы во входном потоке, но не сообщает, будет ли ваше следующее чтение успешным:

while (is.peek()!=EOF) {
    int a;
    is >> a;
    // Still need to test `is` to verify that the read succeeded
}

is >> a может потерпеть неудачу для числа из-за причин, например, на самом деле ввод может быть не числом.

Так что нет никакого смысла в этом, если бы вы могли вместо этого сделать

int a;
while (is >> a) { // reads until failure of any kind
    // use `a`
}

или, может быть, лучше:

for (int a; is >> a;) { // reads until failure of any kind
    // use `a`
}

или ваш первый пример, в этом случае is.peek()!=EOF в l oop станет избыточным.

Предполагается, что вы хотите, чтобы l oop выходил при каждом сбое, следуя вашему первый пример кода, не только в конце файла.

...