2

I have some code which uses std::ifstream to read from a file, but doesn't use any of the formatting features provided by std::ifstream. It essentially does something like this:

std::ifstream in;
in.open("example.txt", std::ios_base::in | std::ios_base::binary);
if (in.is_open()) {
    while (in.good()) {
        in.read(buffer, buffer_size);
        auto num_bytes = in.gcount();
        /* do stuff with the bytes */
    }
    in.close();
}

Since I'm directly processing the raw bytes from the file, it seems like a better fit to use std::filebuf instead of std::ifstream:

std::filebuf in;
if (in.open("example.txt", std::ios_base::in | std::ios_base::binary) != nullptr) {
    while (true) {
        auto num_bytes = in.sgetn(buffer, buffer_size);
        /* do stuff with the bytes */
        if (num_bytes < buffer_size) break;
    }
    in.close();
}

On the surface, it might look like these two code snippets achieve the same outcome. However, upon inspection of the C++ reference, std::basic_istream::read() says this:

If an internal operation throws an exception, it is caught and badbit is set. If exceptions() is set for badbit, the exception is rethrown.

Since badbit is used to signal that the underlying file is broken for some reason (maybe the OS can't access the file any more), it appears that the std::ifstream code snippet would handle this possibility by breaking out of the loop (good() will return false).

However, std::basic_streambuf::sgetn() doesn't say anything about the possibility that the underlying file becomes inaccessible. It also does not mention the possiblity of any exceptions being thrown (but it is not marked as noexcept).

Is there a way, when using std::filebuf, to correctly handle the case when the file is prematurely inaccessible (i.e. not yet EOF)? (Or perhaps, my understanding of file I/O is wrong?)

Bernard
  • 5,209
  • 1
  • 34
  • 64
  • 1
    This doesn't address the question, but there's a lot of redundant code here. `std::ifstream in; in.open("example.txt"...);` can be replaced by `std::ifstream in("example.txt"...);`. The test `if (in.is_open())` is unnecessary; if the stream isn't open, `while(in.good())` will fail if the stream isn't open. And `in.close()` isn't needed; the destructor will close the file. – Pete Becker Jul 14 '18 at 17:03
  • `filebuf::sgetn` -> `basic_streambuf::xsgetn` -> `basic_streambuf::uflow` -> `basic_filebuf::underflow`. [llvm implementation](https://github.com/llvm-mirror/libcxx/blob/master/include/fstream#L671) seems broken, it does not handle `fread` retuning error (<0), strange. gcc implementation of [underflow](https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/bits/fstream.tcc#L465) seems to be throwing base_ios::failure exception on all errors. – KamilCuk Jul 14 '18 at 17:19
  • @KamilCuk I don't think [`fread()`](https://en.cppreference.com/w/cpp/io/c/fread) will return a negative integer on error. According to the documentation, it returns `size_t` which is unsigned. I would expect it to return something in range `[0, count]` even when there is an error; I would then call `ferror()` to check if an error occured. – Bernard Jul 16 '18 at 08:56
  • och, you are right, `fread` from C returns `size_t`, I mixed it with `read` from POSIX, which returns `ssize_t`, sorry, my bad. Anyway llvm implementation does not throw any exceptions on `fread()=0` case. – KamilCuk Jul 16 '18 at 09:02
  • @KamilCuk Yeah, POSIX would return -1 on error. But the `fread() == 0` case also might not be an error. It could be that we consumed exactly the last byte in the previous call to `fread()`, so the current call has no more bytes to read. – Bernard Jul 16 '18 at 09:13

1 Answers1

1

std::filebuf doesn't seem to have any form of error handling / reporting at all. That's just pathetic. I mean, why not?

So, to use it, the only option would be to fall back on good old errno, and since the entry at cppreference doesn't mention that at all, I guess you can't really rely on it (although I'm sure that, in practise, it works).

So, std::ifstream has to be the way to go, if you can plough your way through that mammoth and somewhat obscure list of which exception bit does what. And of course, if you do things this way, you get to control whether exceptions are thrown or not.

Again, there's no apparent way to retrieve any sort of error code when something does go wrong, so again, if you want to try to present any sort of meaningful error message to your user (or for the benefit of tech support, perhaps), then errno is the only game in town.

This is all just going to lead to a proliferation of 'Something went wrong' error messages. Yuk.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • This is rather appalling though. `std::ifstream` has so much stuff over `std::filebuf` that I don't need to use. Maybe I'll just do C-style `fopen()`, `fread()`, `feof()`, and `ferror()`. – Bernard Jul 16 '18 at 08:52
  • Yeah, I do the same when I want to just read the file as bytes. `ostream` / `ofstream` have some useful features. `istream` / `ifstream` less so. – Paul Sanders Jul 16 '18 at 12:24