2

My question is very similar to a previous one. I want to open and read a file. I want exceptions thrown if the file can't be opened, but I don't want exceptions thrown on EOF. fstreams seem to give you independent control over whether exceptions are thrown on EOF, on failures, and on other bad things, but it appears that EOF tends to also get mapped to the bad and/or fail exceptions.

Here's a stripped-down example of what I was trying to do. The function f() is supposed to return true if a file contains a certain word, false if it doesn't, and throw an exception if (say) the file doesn't exist.

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

using namespace std;

bool f(const char *file)
{
    ifstream ifs;
    string word;

    ifs.exceptions(ifstream::failbit | ifstream::badbit);
    ifs.open(file);

    while(ifs >> word) {
        if(word == "foo") return true;
    }
    return false;
}

int main(int argc, char **argv)
{
    try {
        bool r = f(argv[1]);
        cout << "f returned " << r << endl;
    } catch(...) {
        cerr << "exception" << endl;
    }
}

But it doesn't work, because basic fstream reading using operator>> is evidently one of the operations for which EOF sets the bad or the fail bit. If the file exists and does not contain "foo" the function does not return false as desired, but rather throws an exception.

Community
  • 1
  • 1
Steve Summit
  • 45,437
  • 7
  • 70
  • 103

3 Answers3

3

The std::ios_base::failbit flag is also set when there's an attempted extraction when the file has reached the end, something which the behavior of the stream's boolean operator allows. You should set up an extra try-catch block in f() and rethrow the exception if it doesn't correspond with the end of file condition:

std::ifstream ifs;
std::string word;

ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit);

try {
    ifs.open(file);
    while (ifs >> word) {
        if (word == "foo") return true;
    }
 }
catch (std::ios_base::failure&) {
    if (!ifs.eof())
        throw;
}
return false;
David G
  • 94,763
  • 41
  • 167
  • 253
  • Just calling std::find won't work, because the actual code is doing a slightly more elaborate analysis of the file than just looking for one word. P.S. I like your username. 1131770981. – Steve Summit Aug 09 '14 at 11:29
  • To your first suggestion, rather than `while(ifs >> word) { ... }`, I'd have to use something like `while(true) { try { if(!(ifs >> word)) break; } catch (...) { ... } ...` ? – Steve Summit Aug 09 '14 at 11:37
  • @SteveSummit Why would you have to use something like that? – David G Aug 09 '14 at 13:34
  • Never mind, I misunderstood your suggestion. (When you said "extra try-catch block", I was thinking of a try/catch block inside the loop, and I didn't see that the code you presented did have the necessary "extra try-catch block", inside f() but outside the loop.) – Steve Summit Aug 09 '14 at 14:27
1

If the goal is to throw an exception only in case of a problem when opening the file, why not write:

bool f(const char *file)
{
    ifstream ifs;
    string word;

    ifs.open(file);
    if (ifs.fail())  // throw only when needed 
        throw std::exception("Cannot open file !");  // more accurate exception

    while (ifs >> word) {
        if (word == "foo") return true;
    }
    return false;
}

You could of course set :

ifs.exceptions(ifstream::badbit);

before or after the the open, to throw an exception in case something really bad would happen during the reading.

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thanks. This is a very good suggestion, probably the most straightforward for my situation. (I think it _ought_ to be possible to do better, but as @Igor Tandetnik has explained, the C++ behavior I'm butting up against is pretty fundamental. And there's also a very real tradeoff between desperately trying to achieve precisely the behavior I thought I wanted, using all sorts of custom exception handling working around C++'s various idiosyncrasies, versus keeping the code at all readable.) – Steve Summit Aug 09 '14 at 11:20
  • 1
    One other thing, though: I don't agree that "Cannot open file!" is a more accurate exception. If I let `ifs.open` throw its own exceptions, there would presuably be useful extra information about _why_ it couldn't open it, like "Permission denied" or "No such file or directory". (For once, though, in this particular case I'm not going to mind not having that extra information, which would never be seen by the user anyway.) – Steve Summit Aug 09 '14 at 11:56
  • That's true ! But it depends on implementation : for example the standard exception of MSVC13 has `e.what()` returning only *"Exception ios_base::failbit set: iostream stream error"* when the file is missing. An alternative could however be to `throw std::exception(strerror(errno));` since C++11 comes with a longer portable error message list. – Christophe Aug 09 '14 at 14:53
1

basic_ios::operator bool() checks fail(), not !good(). Your loop tries to read one more word after EOF is reached. operator>>(stream&, string&) sets failbit if no characters were extracted. That's why you always exit with an exception.

It's hard to avoid that though. The stream reaches EOF state not when the last character is read, but when an attempt is made to read past the last character. If that happens in the middle of a word, then failbit is not set. If it happens in the beginning (e.g. if the input has trailing whitespace), then failbit is set. You can't really reliably end up in eof() && !fail() state.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Thanks very much for this explanation (and for a citation from the Standard I saw earlier, which someone -- maybe you -- seems to have posted and then deleted). Does my innocent-looking code (which I inherited from someone else, btw) really try to read past the end after EOF? Dang. That's a cardinal sin, in my book. No wonder it doesn't work. – Steve Summit Aug 09 '14 at 11:27