Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 831
gnikit Avatar asked Jun 03 '18 02:06

gnikit


2 Answers

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';
    }
like image 157
David C. Rankin Avatar answered Oct 11 '22 02:10

David C. Rankin


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.

like image 40
jcai Avatar answered Oct 11 '22 03:10

jcai