TRT
Sign in

Foundations · Module 1 · Lesson 5 of 9

Reading Input and Surviving Bad Input

Read values with std::cin, see what a failed read does to the variable and to the stream, and build the clear-and-discard loop that survives bad input.

FreeAbout 9 minutes

By the end you can

  • Explain what an extraction like std::cin >> age consumes and what it leaves unread
  • Describe the stream state after a failed read and why later reads do nothing
  • Apply clear and ignore, in the right order, to recover from a failed read
  • Predict the leftover-newline problem when mixing extraction with std::getline

Builds on Types Decide What Bits Mean.

01 · A question first

A user types abc where your program expects a number. Does the program crash, stop with an error, or carry on? And why can the obvious fix, asking again in a loop, spin forever without ever waiting for the user?

Make a guess, then step through the trace below to check it.

02 · Watch it run

One bad line, three reads and a repairstep 1 of 6
int a = 0;int b = 0;std::cin >> a;std::cin >> b;std::cin >> a;std::cin.clear();std::cin.ignore(1000, '\n');
  1. char 04
  2. char 12
  3. char 2
  4. char 3x
  5. char 47
  6. char 5NL

The user typed 42 x7 and pressed Enter. Those six characters, including the final newline, wait in the input buffer. Each read consumes characters from the front and stops as soon as the rules of its type say stop.

Machine work: stream state: good; a is 0, b is 0

03 · The idea

Start with the problem

A program that only prints is a calculator with no keys. Reading input is its first contact with the outside world, and the outside world does not follow your assumptions. Here is a program that asks for an age:

cpp
#include <iostream>
 
int main() {
    int age = 0;
    std::cout << "Age: ";
    std::cin >> age;
    std::cout << "Next year: " << age + 1 << "\n";
}

Type 31 and it answers Next year: 32. Type abc and it answers Next year: 1. There is no crash, no error message and no exit status to warn anyone: the program carried on with a wrong value as if nothing had happened. Making a program survive bad input means knowing exactly what a read does when it fails.

How the idea works

The trace above feeds the line 42 x7 to three reads and then repairs the stream. Here are the pieces.

Input streams. std::cin is the input twin of std::cout. The >> symbol is the extraction operator: it takes characters from the stream, converts them to the type of the variable on its right, and returns the stream, so std::cin >> a >> b chains as << does. In a terminal the characters reach the program one whole line at a time, when you press Enter. Until then the program is blocked, and afterward the characters wait in a buffer, as in the first step of the trace.

The type decides the parsing. This is lesson 4 working in reverse. For an int, the extraction skips leading whitespace, accepts an optional sign and digits, and stops at the first character that cannot continue a number, leaving that character unread. For a std::string it reads up to the next whitespace. For a char it reads a single character. The same keystrokes mean different things depending on the variable that receives them.

Stream state. Every stream carries flags that describe what has happened to it. The two you will meet constantly are failbit, set when an extraction could not produce a value, and eofbit, set when the input has ended. If an integer read finds no digits, the stream sets failbit, writes 0 into the variable and leaves the offending character unread, as in step 3. If the digits form a number too large for the type, it stores the largest or smallest value of that type and also sets failbit. Putting a stream in a condition, as in if (std::cin >> age), asks whether the read succeeded.

A failed stream stays failed. While failbit is set, every later extraction does nothing at all, as in step 4. To recover you need two calls, in this order. std::cin.clear() resets the flags (step 5), and std::cin.ignore(count, '\n') discards characters up to and including the next newline (step 6). Without the second call the bad character is still first in line and the next read fails again. Without the first, the discard is skipped, because a failed stream ignores that too.

Together they give the standard retry loop, which belongs inside main and needs the header <limits>:

cpp
int age = 0;
while (true) {
    std::cout << "Age: ";
    if (std::cin >> age && age >= 0) break;
    if (std::cin.eof()) return 1;
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cout << "Please enter a non-negative whole number.\n";
}

The end-of-input check matters. If the user closes the input stream, no repair can help, so the program stops with a nonzero exit status, as lesson 2 described.

Whole lines. std::getline(std::cin, line) reads everything up to the end of the line into a std::string and discards the newline. It is the right tool for text that contains spaces, such as names.

Why it is correct

The design makes failure visible without making it fatal. A read returns the stream itself, and a stream converts to a true-or-false answer, so if (std::cin >> x) reads and checks in a single expression. Bad input is not an exception by default: with a human at the keyboard it is an ordinary event, so the default is a flag you check, not an error that unwinds the program. Exceptions can be switched on with std::cin.exceptions(std::ios::failbit), and a later module returns to that choice.

Leaving the offending character unread is deliberate. The stream cannot know whether you want to skip it, report it, or parse it differently, so it stops and hands the decision back, and the sticky failed state makes sure you cannot continue by accident on half-read data. The repair is explicit because only your program knows what a good repair is.

What it costs

Correctness code, not time. Checking a stream flag is free. The price of robust input is the extra lines for validation and recovery, and it is a price worth paying every time.

Bulk input is slower than it looks. By default std::cin is synchronized with the C library's input, and it is tied to std::cout, which means the output buffer is flushed before every read. That tie is why a prompt such as "Age: " with no newline still appears before the program waits for a reply. For programs that read millions of numbers, calling std::ios::sync_with_stdio(false) and std::cin.tie(nullptr) before the first read commonly makes input several times faster. Use it for bulk input only, because it removes the automatic flush that makes prompts appear.

Allocation. std::getline grows a std::string as needed, which may allocate memory. That is negligible for interactive input and worth knowing for very large files.

Pitfalls and variations

The silent zero. Not checking the result is how the program at the top printed Next year: 1. Always test the read.

The endless retry. A loop that prompts again without calling clear and ignore spins forever: every read fails instantly on the same unread character, and the program never waits for the user.

The leftover newline. After std::cin >> n the newline the user typed stays in the buffer. A following std::getline finds it immediately and returns an empty string without waiting. Discard the newline with std::cin.ignore(...) first, or read every line with std::getline and convert the text yourself.

Success means a prefix parsed. Typing 42abc into an int succeeds with 42 and leaves abc for the next read, and typing 3.7 gives 3 and leaves .7. If the whole line must be valid, read it with std::getline and parse it, checking that nothing is left over.

The eof loop. while (!std::cin.eof()) is a classic bug: the flag is set only after a read has already failed, so the loop body runs once too often. Use the read itself as the condition: while (std::cin >> x).

Variations. std::stoi converts a string to an int but throws an exception on failure. std::from_chars, in the header <charconv> since C++17, parses without exceptions or locale handling and is the fast choice for large inputs. The same extraction syntax works on files through std::ifstream, which a later lesson covers.

Where it is used

Every command-line tool reads input this way, and the habit of making the read the loop condition drives most competitive-programming and data-processing code, as in while (std::cin >> value). Because a stream does not care where its characters come from, the same program works unchanged with a file: run it as ./age < input.txt and the shell feeds the file to std::cin. That is also how you will test input code automatically in later modules.

The next lesson, "Reading Compiler Errors and Warnings", turns to the messages your tools print, including the ones that appear when input code like this is written wrongly.

04 · Prove it

Question 1 of 4 · answer from memory

A user types `42abc` and the program runs `int n = 0; std::cin >> n;`. What happens?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save