Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Example of Why stream::good is Wrong?

I gave an answer which I wanted to check the validity of stream each time through a loop here.

My original code used good and looked similar to this:

ifstream foo("foo.txt");

while (foo.good()){
    string bar;
    getline(foo, bar);
    cout << bar << endl;
}

I was immediately pointed here and told to never test good. Clearly this is something I haven't understood but I want to be doing my file I/O correctly.

I tested my code out with several examples and couldn't make the good-testing code fail.

First (this printed correctly, ending with a new line):

bleck 1
blee 1 2
blah
ends in new line

Second (this printed correctly, ending in with the last line):

bleck 1
blee 1 2
blah
this doesn't end in a new line

Third was an empty file (this printed correctly, a single newline.)

Fourth was a missing file (this correctly printed nothing.)

Can someone help me with an example that demonstrates why good-testing shouldn't be done?

like image 883
Jonathan Mee Avatar asked Feb 03 '15 13:02

Jonathan Mee


2 Answers

They were wrong. The mantra is 'never test .eof()'.

  • Why is iostream::eof inside a loop condition considered wrong?

Even that mantra is overboard, because both are useful to diagnose the state of the stream after an extraction failed.

So the mantra should be more like

Don't use good() or eof() to detect eof before you try to read any further

Same for fail(), and bad()

Of course stream.good can be usefully employed before using a stream (e.g. in case the stream is a filestream which has not been successfully opened)

However, both are very very very often abused to detect the end of input, and that's not how it works.


A canonical example of why you shouldn't use this method:

std::istringstream stream("a");
char ch;
if (stream >> ch) {
   std::cout << "At eof? " << std::boolalpha << stream.eof() << "\n";
   std::cout << "good? " << std::boolalpha << stream.good() << "\n";
}

Prints

false
true

See it Live On Coliru

like image 184
sehe Avatar answered Sep 21 '22 13:09

sehe


This is already covered in other answers, but I'll go over it briefly for completeness. The only functional difference with

while(foo.good()) { // effectively same as while(foo) {
    getline(foo, bar);
    consume(bar); // consume() represents any operation that uses bar
}

And

while(getline(foo, bar)){
    consume(bar);
}

Is that the former will do an extra loop when there are no lines in the file, making that case indistinguishable from the case of one empty line. I would argue that this is not typically desired behaviour. But I suppose that's matter of opinion.

As sehe says, the mantra is overboard. It's a simplification. What really is the point is that you must not consume() the result of reading the stream before you test for failure or at least EOF (and any test before the read is irrelevant). Which is what people easily do when they test good() in the loop condition.

However, the thing about getline(), is that it tests EOF internally, for you and returns an empty string even if only EOF is read. Therefore, the former version could maybe be roughly the similar to following pseudo c++:

while(foo.good()) {
    // inside getline
    bar = "";               // Reset bar to empty
    string sentry;
    if(read_until_newline(foo, sentry)) {
        // The streams state is tested implicitly inside getline
        // after the value is read. Good
        bar = sentry        // The read value is used only if it's valid.
    // ...                  // Otherwise, bar is empty.
    consume(bar);
}

I hope that illustrates what I'm trying to say. One could say that there is a "correct" version of the read loop inside getline(). This is why the rule is at least partially satisfied by the use of readline even if the outer loop doesn't conform.

But, for other methods of reading, breaking the rule hurts more. Consider:

while(foo.good()) {
    int bar;
    foo >> bar;
    consume(bar);
}

Not only do you always get the extra iteration, the bar in that iteration is uninitialized!

So, in short, while(foo.good()) is OK in your case, because getline() unlike certain other reading functions, leaves the output in a valid state after reading EOF bit. and because you don't care or even do expect the extra iteration when the file is empty.

like image 43
eerorika Avatar answered Sep 19 '22 13:09

eerorika