Skip to Content
Course content

200: Error Handling for File Streams

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've spent a fair amount of time reviewing PRs where the developer assumes that if a file opens successfully, the rest of the operation is a given. It's a dangerous assumption. In C++, file streams don't just fail when the file is missing; they fail when the disk fills up, when the network drive disconnects mid-read, or—most commonly—when the data inside the file isn't what you expected it to be.

The "Open and Pray" Strategy

The most common naive approach I see is the one where the programmer checks is_open() at the start and then assumes the stream is a reliable pipe until the end of the file. Let's say we're writing a utility to read a list of server ports from a configuration file. A typical "wrong" implementation looks like this:

std::ifstream portFile("ports.txt");
if (!portFile.is_open()) {
    std::cerr << "Could not open file!" << std::endl;
    return 1;
}

int port;
while (!portFile.eof()) {
    portFile >> port;
    std::cout << "Loading port: " << port << std::endl;
}

At first glance, this looks logical. You check if the file exists, and you loop until you hit the end. But this is a ticking time bomb. First, eof() only returns true after a read operation has already attempted to go past the end of the file. This means your loop will almost always execute one extra time, processing the last piece of data twice or processing garbage data.

More importantly, what happens if ports.txt contains a typo? If someone accidentally types "8080a" instead of "8080", the >> operator will fail to parse the integer. The stream enters a "fail state," and every subsequent read will immediately fail. But because you're only checking eof(), and the stream hasn't actually reached the end of the file—it's just stuck on that 'a'—your program will enter an infinite loop, printing the last successful port value forever.

Letting the Stream Tell You the Truth

The better way—the way we do it in production code—is to treat the read operation itself as the condition. In C++, stream objects can be evaluated as booleans. When you do this, the stream checks its internal state flags (failbit and badbit) and tells you if the last operation actually worked.

I prefer this pattern because it combines the "did we read something?" check with the "is the stream still healthy?" check into one clean expression:

std::ifstream portFile("ports.txt");
if (!portFile) {
    std::cerr << "File access error." << std::endl;
    return 1;
}

int port;
while (portFile >> port) {
    std::cout << "Loading port: " << port << std::endl;
}

if (portFile.bad()) {
    std::cerr << "A critical hardware or system error occurred." << std::endl;
} else if (!portFile.eof()) {
    std::cerr << "Data corruption detected: non-numeric value found." << std::endl;
}

Notice the difference. By placing portFile >> port inside the while condition, the loop terminates the moment a read fails for any reason—whether it's the end of the file or a formatting error. It's tighter, safer, and avoids that annoying double-processing of the last line.

Distinguishing Between Failures

Now, you might wonder why I added those checks after the loop. In a real system, you need to know why it stopped. There's a huge difference between "we finished the list" and "the file contains gibberish."

  • failbit: This is the "soft" failure. It happens when the formatting is wrong (like reading a string into an int). The stream is still physically open, but the data is bad.
  • badbit: This is the "hard" failure. This happens when something goes catastrophically wrong, like a disk failure or loss of connection to a network share.

By checking bad() and eof() after the loop exits, you can provide meaningful feedback to the user. I've seen developers ignore this and just print "Error reading file," which leaves the sysadmin guessing whether they need to fix a typo in the config or replace a dying hard drive. Be specific; your future self will thank you when you're debugging a production crash at 3 AM.




📋 Practical Task

Exercise: The Robust Log Parser

You are tasked with building a log parser that reads a file named system_logs.txt. The file is supposed to contain a sequence of integers representing error codes, but in the real world, these files often get corrupted with random text strings or null characters.

Write a program that does the following:

  1. Opens system_logs.txt.
  2. Reads the error codes one by one using a while loop that evaluates the stream state.
  3. Stores the valid error codes in a std::vector<int>.
  4. After the loop ends, the program must determine the exact cause of the termination:
    • If the end of the file was reached successfully, print: "Parsing complete. [X] codes loaded." (where X is the count).
    • If the loop stopped because of a formatting error (non-integer data), print: "Parsing aborted: Invalid data encountered."
    • If a critical stream error occurred, print: "Parsing aborted: System failure."

Test your code with a file that contains 101 404 500 error_here 200 to ensure your error handling catches the "error_here" string and stops correctly.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.