89 / 163 · C++11 · 13 min
Input parsing: read the complete record, validate then commit
Reading an integer from a stream does not prove the entire record is legal. Divide line acquisition, field extraction, range checking, and result committing into stages, so that extra fields, negative values, and bad formats all leave interpretable failure results, without polluting the previous valid record.
In this lesson
Programming: Principles and Practice Using C++
Read all available text, code, Drill, Review, Exercises, terms/index of second edition chapters 0–27 and appendices A–E; among them chapters 1, 11, 22–25, 27 and Glossary were additionally read using the author's complete chapter PDFs. Author chapters total 294 printed pages; key missing pages, tables, and figures in chapters 11/22/23 have been supplemented by reading images. The remaining public full-book OCR still has damaged code and unrecovered graphics/tables, especially chapters 12–16 graphics and GUI, therefore it is not claimed that the original edition was read through without gaps in text and images; exercises were not run.
Edition, actual reading range, and original sources →A successful extraction does not equal a legal record
An input stream can convert text into types, but does not know that the business requires a line to contain several fields. If only name and quantity are read, the first two fields of the string alpha 3 extra can still be extracted successfully; if only non-negative quantities are accepted, integer extraction will not exclude negatives for you. In the book, going from input errors to class invariants, the key is precisely to separate these layers.
This example stipulates that a line has exactly one name without whitespace and one non-negative integer, allowing trailing whitespace, not accepting extra fields. The caller is responsible for obtaining the entire line, the parsing function only processes this limited record. Therefore a bad record will not stop the shared input stream in the middle of a field, nor misread the next line as a supplement to the current record.
Read candidate values first, then check the end position
The function extracts fields into local variables, any failure immediately returns false. Then use std::ws to consume allowed trailing whitespace, and check whether the end of the record has been reached. Thus numeric suffixes, extra fields, and incomplete input are all rejected, while newline acquisition and field syntax remain independent of each other.
Only after all checks pass, swap the name and write the integer. If the previous string allocation throws an exception, the old record has not changed either; the swap of default-allocator string and int assignment here will not introduce new allocation failures. This simple commit step supports a clear promise of no modification on failure, without needing to manually restore old fields in every error branch.
Error strategy should be left to the layer that knows the context
The parsing function reports illegal syntax or range with a boolean result, the calling layer can display errors, skip the record, or require re-input; exceptions such as resource exhaustion still propagate naturally, without mixing all failures into zero quantity. The example compares the complete old values after each bad input one by one, cannot just check “returned false”.
PPP second edition uses C++11 and some C++14 techniques, the graphics library is the textbook's wrapper for FLTK, not a standard graphics interface. Here standard string streams are chosen, avoiding dependence on textbook headers and graphics environment. In C++20 other parsing facilities can also be used, but no matter how the tools change, the number of fields, numeric range, input end, and commit timing still need to be clearly specified by the interface.
Pitfalls
- Checking eof first then reading in a loop will miss or process one extra time; should first check the actual result of the extraction operation.
- Do not directly modify the output object field by field; when the second field fails it may leave a seemingly valid half new record.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <sstream>
#include <string>
struct Record { std::string name; int count; };
bool parse_record(const std::string& line, Record& out) {
std::istringstream input(line);
std::string name;
int count = 0;
if (!(input >> name >> count) || count < 0) return false;
input >> std::ws;
if (!input.eof()) return false;
out.name.swap(name);
out.count = count;
return true;
}
int main() {
Record record{"old", 8};
assert(parse_record("alpha 3 ", record));
assert(record.name == "alpha" && record.count == 3);
for (const std::string line : {"beta -1", "beta 2 extra", "beta 2x", "beta", ""}) {
assert(!parse_record(line, record));
assert(record.name == "alpha" && record.count == 3);
}
assert(parse_record("zero 0", record));
assert(record.name == "zero" && record.count == 0);
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread books-programming-principles-practice.cpp -o example && ./exampleExpected result
Expected: exit 0, no output; every assert holds.
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Limit the quantity to 0 to 100, and give checks that failure keeps the old values.
Show a reference answer
After extracting to local variables, change the range condition to count < 0 || count > 100; the commit code remains unchanged. First parse good 100 and confirm success, then parse bad 101, require returning false and name still good, quantity still 100; finally parse good 0 to verify the other boundary. Do not change the over-limit quantity back to 100 after committing, that would conceal the input error.
Check the sources
Drafts and official chapters change. The version mark is only the example’s minimum.