C++: strange behavior with std::istream or sentry

2019-05-11 04:25发布

问题:

This small custom getline function was given as an answer to a question about handling different line endings.

The function worked great until it was edited 2 days ago to make it not skip leading white spaces for each line. However, after the edit, the program now goes into an infinite loop. The only change done to the code was this following line which was changed from this:

std::istream::sentry se(is);  // When this line is enabled, the program executes
                              // correctly (no infinite loop) but it does skip
                              // leading white spaces

to this:

std::istream::sentry se(is, true); // With this line enabled, the program goes 
                                   // into infinite loop inside the while loop  
                                   // of the main function.

Can someone please help me explain why the program loops infinitely if we specify to not skip white spaces?

Here is the full program...

std::istream& safeGetline(std::istream& is, std::string& t)
{
    t.clear();

    // The characters in the stream are read one-by-one using a std::streambuf.
    // That is faster than reading them one-by-one using the std::istream.
    // Code that uses streambuf this way must be guarded by a sentry object.
    // The sentry object performs various tasks,
    // such as thread synchronization and updating the stream state.

    std::istream::sentry se(is, true);
    std::streambuf* sb = is.rdbuf();

    for(;;) {
        int c = sb->sbumpc();
        switch (c) {
        case '\r':
            c = sb->sgetc();
            if(c == '\n')
                sb->sbumpc();
            return is;
        case '\n':
        case EOF:
            return is;
        default:
            t += (char)c;
        }
    }
}

And here is a test program:

int main()
{
    std::string path = "end_of_line_test.txt"

    std::ifstream ifs(path.c_str());
    if(!ifs) {
        std::cout << "Failed to open the file." << std::endl;
        return EXIT_FAILURE;
    }

    int n = 0;
    std::string t;
    while(safeGetline(ifs, t))   //<---- INFINITE LOOP happens here. <----
        std::cout << "\nLine " << ++n << ":" << t << std::endl;

    std::cout << "\nThe file contains " << n << " lines." << std::endl;
    return EXIT_SUCCESS;
}

I also tried to add this line at the very beginning of the function but it made no difference... the program still looped infinitely in the while loop of the main function.

is.setf(0, std::ios::skipws);

The file end_of_line_test.txt is a text file which contains only the following two lines:

   "1234" // A line with leading white spaces
"5678"    // A line without leading white spaces

回答1:

The problem is that safeGetLine never sets the eof() state for the stream.

When you use std::istream::sentry se(is);, it will try to read whitespace and discover that you are at end-of-file. When you ask it not to look for whitespace, this never happens.

I believe you should add is.setstate(ios_base::eofbit) to the EOF condition for the function.