gnikit
gnikit

Reputation: 1291

getline setting failbit along with eof

I am aware of the origin of this behavior since it has been very well explained in multiple posts here in SO, some notable examples are:

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

Use getline() without setting failbit

std::getline throwing when it hits eof

C++ istream EOF does not guarantee failbit?

And it is also included in the std::getline standard:

3) If no characters were extracted for whatever reason (not even the discarded delimiter), getline sets failbit and returns.

My question is how does one deal with this behavior, where you want your stream to catch a failbit exception for all cases except the one caused by reaching the eof, of a file with an empty last line. Is there something obvious that I am missing?

A MWE:

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


void f(const std::string & file_name, char comment) {

std::ifstream file(file_name);
file.exceptions(file.failbit);
    try {
          std::string line;

          while (std::getline(file, line).good()) {
          // empty getline sets failbit throwing an exception
            if ((line[0] != comment) && (line.size() != 0)) {
                std::stringstream ss(line);
                // do stuff
            }
        }
    }

    catch (const std::ios_base::failure& e) {
        std::cerr << "Caught an ios_base::failure.\n"
        << "Explanatory string: " << e.what() << '\n'
        << "Error code: " << e.code() << '\n';

        }
}


int main() {

    f("example.txt", '#');
}

where example.txt is a tab-delimited file, with its last line being only the \n char:

# This is a text file meant for testing
0   9
1   8
2   7

EDIT:

while(std::getline(file, line).good()){...} replicates the problem.

Upvotes: 5

Views: 2256

Answers (2)

jcai
jcai

Reputation: 3583

Edit: I misunderstood the OP, refer to David's answer above. This answer is for checking whether or not the file has a terminating newline.

At the end of your while (getline) loop, check for file.eof().

Suppose you just did std::getline() for the last line in the file.

  • If there is a \n after it, then std::getline() has read the delimiter and did not set eofbit. (In this case, the very next std::getline() will set eofbit.)

  • Whereas if there is no \n after it, then std::getline() has read EOF and did set eofbit.

In both cases, the very next std::getline() will trigger failbit and enter your exception handler.

PS: the line if ((line[0] != comment) && (line.size() != 0)) { is UB if line is empty. The conditions' order needs to be reversed.

Upvotes: 2

David C. Rankin
David C. Rankin

Reputation: 84561

Another way to avoid setting failbit, is simply to refactor your if tests to detect the read of an empty-line. Since that is your final line in this case, you can simply return to avoid throwing the error, e.g.:

    std::ifstream file (file_name);
    file.exceptions (file.failbit);
    try {
        std::string line;

        while (std::getline(file, line)) {
            // detect empty line and return
            if (line.size() == 0)
                return;
            if (line[0] != comment) {
                std::stringstream ss(line);
                // do stuff
            }
        }
    }
    ...

You other alternative is to check whether eofbit is set in catch. If eofbit is set -- the read completed successfully. E.g.

    catch (const std::ios_base::failure& e) {
        if (!file.eof())
            std::cerr << "Caught an ios_base::failure.\n"
            << "Explanatory string: " << e.what() << '\n'
            << "Error code: " /* << e.code() */ << '\n';
    }

Upvotes: 3

Related Questions