13

When a beginner starts off reading ifstreams, his/her instinct is to read the file using a loop that usually looks like this:

while (!ifstream.eof()
{
...
}

However, when I used this code I noticed that it didn't stop until it had read the last line of the file twice. C++ programmers note that this isn't actually how one should a read a file. Instead, they usually recommend that whoever needs to read a file use a loop like this instead:

while (ifstream >> someVar)
{
...
}

Why does the first piece of code always fail to work properly?

2

3 Answers 3

6

The while (!ifstream.eof()) loop doesn't work, because streams/files in C and C++ don't predict when you have reached the end of the file, but the rather indicate if you have tried to read past the end of the file.

If the last line of the file ends with a newline (\n) character, then most read action will stop reading when they have encountered that character and they don't detect that it happens to be the last character in the file. On the next read action, it can even be that more characters have been appended and that the read will succeed in extracting them.

The loop using the stream extraction operator (while (ifstream >> someVar)) works because the result from the stream extraction operator evaluated to false if it couldn't extract an item of the right type. This also happens if there are no characters left to read.

7

However, C++ programmers note that what always happens is that cin.eof() doesn't return "true" until after the last line has been read twice.

That is not what is happening. The eofbit plays no a role in the conversion to a boolean (stream::operator bool (or operator void* in older c++)). Only the badbit and failbit are involved.

Suppose you are reading a file containing numbers separated by whitespace. A loop based around cin.eof() will inevitably be either wrong or be chock full of if tests. You aren't reading until EOF. You are reading numbers. So make your code express that logic:

while (stream >> some_var) {
    process_value(some_var);
}

This will work whether the last line of the file ends with 0 42\n or just 0 42 (no new line at the end of the last line in the file). If the file ends with 0 42\n, the last good read will retrieve the value 42 and read that final end of line marker. Note that the EOF marker has not yet been read. The function process_value is called with 42. The next call to the stream extraction operator >> reads the EOF, and since nothing has been extracted, both the eofbit and failbit will be set.

Suppose on the other hand, the file ends with 0 42 (no newline at the end of the last line). The last good read will retrieve the value 42 terminating on the EOF marker. Presumably you want to process that 42. This is why the eofbit does not play a role in the input stream boolean conversion operator. On the next call to the stream extraction operator >>, the underlying machinery quickly sees that the eofbit is already set. This quickly results in setting the failbit.

Why does the first piece of code always fail to work properly?

Because you shouldn't be checking for EOF as the loop condition. The loop condition should express what you are trying to do, which is (for example), extracting numbers from a stream.

1

One way to solve this is to use std::istream::peek() to detect the end of file by reading ahead without moving the file position.

while(!ifstream.eof())
{
    // Read something here.
    ...
    ifstream.peek();
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.