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
while(std::getline(file, line).good()){...}
replicates the problem.
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';
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With