Semantics of flags on basic_ios
Asked Answered
N

2

28

I find myself repeatedly baffled by the rdstate() flags - good(), bad(), eof(), fail() - and how they are expressed in basic_ios::operator!, operator bool and operator void*.

Could somebody put me out of my misery and explain this so I never have to think twice again?

Nero answered 23/11, 2010 at 17:23 Comment(0)
P
24

There are three flags that indicate error state:

  • badbit means something has gone very wrong with the stream. It might be a buffer error or an error in whatever is feeding data to the stream. If this flag is set, it's likely that you aren't going to be using the stream anymore.

  • failbit means that an extraction or a read from the stream failed (or a write or insertion for output streams) and you need to be aware of that failure.

  • eofbit means the input stream has reached its end and there is nothing left to read. Note that this is set only after you attempt to read from an input stream that has reached its end (that is, it is set when an error occurs because you try to read data that isn't there).

The failbit may also be set by many operations that reach EOF. For example, if there is only whitespace left remaining in the stream and you try to read an int, you will both reach EOF and you will fail to read the int, so both flags will be set.

The fail() function tests badbit || failbit.

The good() function tests !(badbit || failbit || eofbit). That is, a stream is good when none of the bits are set.

You can reset the flags by using the ios::clear() member function; this allows you to set any of the error flags; by default (with no argument), it clears all three flags.

Streams do not overload operator bool(); operator void*() is used to implement a somewhat broken version of the safe bool idiom. This operator overload returns null if badbit or failbit is set, and non-null otherwise. You can use this to support the idiom of testing the success of an extraction as the condition of a loop or other control flow statement:

if (std::cin >> x) {
    // extraction succeeded
}
else {
    // extraction failed
}

The operator!() overload is the opposite of the operator void*(); it returns true if the badbit or failbit is set and false otherwise. The operator!() overload is not really needed anymore; it dates back to before operator overloads were supported completely and consistently (see sbi's question "Why does std::basic_ios overload the unary logical negation operator?").

C++0x fixes the problem that causes us to have to use the safe bool idiom, so in C++0x the basic_ios base class template does overload operator bool() as an explicit conversion operator; this operator has the same semantics as the current operator void*().

Pool answered 23/11, 2010 at 17:27 Comment(1)
Good is misnamed (and thus misapplied), because testing good() doesn't tell you whether the last operation succeeded or not. In other words, stream.good() is not equivalent to bool(stream).Fischer
A
17

In addition to James' answer, it's important to remember that these flags indicate results of operations, so won't be set unless you perform one.

A common error is to do this:

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    while (!file.eof()) // while the file isn't at eof...
    {
        std::string line;
        std::getline(file, line); // ...read a line...

        std::cout << "> " << line << std::endl; // and print it
    }
}

The problem here is that eof() won't be set until after we try to get the last line, at which point the stream will say "nope, no more!" and set it. This means the "correct" way is:

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    for (;;)
    {
        std::string line;
        std::getline(file, line); // read a line...

        if (file.eof()) // ...and check if it we were at eof
            break;

        std::cout << "> " << line << std::endl;
    }
}

This places the check in the correct location. This is very unruly though; luckily for us, the return value for std::getline is the stream, and the stream has a conversion operator that allows it to be tested in a boolean context, with the value of fail(), which includes eof(). So we can just write:

#include <fstream>
#include <iostream>
#include <string>

int main()
{
    std::ifstream file("main.cpp");

    std::string line;
    while (std::getline(file, line)) // get line, test if it was eof
        std::cout << "> " << line << std::endl;
}
Appraisal answered 23/11, 2010 at 17:47 Comment(2)
+1 because it's important to highlight this. I/O is done wrongly far too frequently in C++.Pool
Many thanks, this issue came up on a q yesterday too and (surprise!) baffled me initially, although it does make sense once you think about it.Nero

© 2022 - 2024 — McMap. All rights reserved.