55

I'm used to the Delphi VCL Framework, where TStreams throw exceptions on errors (e.g file not found, disk full). I'm porting some code to use C++ STL instead, and have been caught out by iostreams NOT throwing exceptions by default, but setting badbit/failbit flags instead.

Two questions...

a: Why is this - It seems an odd design decision for a language built with exceptions in it from day one?

b: How best to avoid this? I could produce shim classes that throw as I would expect, but this feels like reinventing the wheel. Maybe there's a BOOST library that does this in a saner fashion?

Roddy
  • 66,617
  • 42
  • 165
  • 277
  • 2
    iostream is part of the C++ standard library, the STL is a subset of the C++ standard library but iostream is not part of the STL subset. – Clifford May 31 '17 at 12:24

4 Answers4

76
  1. C++ wasn't built with exceptions from day one. "C with classes" started in 1979, and exceptions were added in 1989. Meanwhile, the streams library was written as early as 1984 (later becomes iostreams in 1989 (later reimplemented by GNU in 1991)), it just cannot use exception handling in the beginning.

    Ref:

  2. You can enable exceptions with the .exceptions method.

// ios::exceptions
#include <iostream>
#include <fstream>
#include <string>

int main () {
    std::ifstream file;
    file.exceptions(ifstream::failbit | ifstream::badbit);
    try {
        file.open ("test.txt");
        std::string buf;
        while (std::getline(file, buf))
            std::cout << "Read> " << buf << "\n";
    }
    catch (ifstream::failure& e) {
        std::cout << "Exception opening/reading file\n";
    }
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • But this will of course hinder the use of normal stream idioms like `while( stream >> token )`, as a badbit or eofbit will cause an exception to be thrown. And in your example it would be better to do `while(getline(file, buffer))` instead of checking for eof() explicitly, as other bits may be set as well. – rubenvb Jul 05 '10 at 14:38
  • 9
    `file.close()` - do you need that? I was expecting they were smart enough to close on destruction...??? – Roddy Jul 05 '10 at 14:51
  • 2
    The example is a bit crappy. If you have enabled eof exceptions, why test (incorrectly) for eof? –  Jul 05 '10 at 14:55
  • 5
    @Roddy close() will be called by the streams destuctor. However, it's always a good idea to say what you mean explicitly. –  Jul 05 '10 at 14:56
  • 23
    @Neil. Thanks - but disagree to explicitly close()ing - it would be like explicitly deleting autoptr objects! – Roddy Jul 05 '10 at 15:02
  • The 'history' is interesting, but I thought templates (which are I thought were fundamental to iostreams) were also a very late addition? – Roddy Jul 05 '10 at 15:39
  • 2
    Originally, the streams weren't templated. That was tacked on after the fact. – Dennis Zickefoose Jul 05 '10 at 15:56
  • 18
    @Roddy: Yes, they will close themselves on destruction, but they will also catch all exception which *might be thrown* by `flush()`. If it's a log file, it's fine. If it's a document 'Save' command, then you really want to be sure the file is closed, and if flushing failed report it to the user. `closing()` a stream is like committing a transaction, or like `swap()`ing in a copy&swap assignment operator implementation. This "commit" step is common in C++. – Yakov Galka Mar 23 '12 at 20:24
  • In case anyone is having trouble catching the exception in GCC, it's a bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145 – LB-- Apr 23 '16 at 01:29
  • Enabling exceptions for the failbit leads to an exception when the getline() call reaches EOF because the eofbit often also sets the failbit. Most probably this is not the desired behavior. – oli_arborum Feb 09 '18 at 09:44
  • 1
    Shouldn't file.close(), or at least file.flush(), be inside the try block? In the event of one exception close will be called anyway by the stream destructor (RAII). – Adriel Jr Feb 15 '18 at 14:30
  • 1
    @YakovGalka, 1) This is an input stream. It won't flush and so won't throw any exception. `file.close` is absolutely useless here. 2) If it is was output stream, then `file.close` should be inside try-block. I would place entire `std::ifstream file` insde try-block. – anton_rh Oct 23 '20 at 10:06
  • @anton_rh: I agree and thank you for pointing this out! Just to clarify: my comment refers to calling flush on **output** streams. Explicitly closing or flushing **input** streams, like in the answer above, isn't necessary. And the `try/catch` block will go somewhere above the call-stack where it'll be reported to the user -- not handling exceptions explicitly is a good rule of thumb. – Yakov Galka Oct 23 '20 at 17:05
  • How do you disable it again after the call of `exceptions()`? – aleck099 Apr 08 '22 at 08:40
5

OK, it's "Answer my own question" time...

First, thanks to KennyTM for the history. As he says, C++ was NOT designed with exceptions from day one, so it's unsurprising that iostreams 'exception' handling was bolted on afterwards.

Second, as Neil B points out, having exceptions on input format conversion errors would be a significant pain. This surprised me, because I was considering iostreams as a simple filesystem wrapper layer, and I hadn't considered that case at all.

Third, it appears BOOST does bring something to the party: Boost.IOStreams. If I understand correctly, these handle the low-level I/O and buffering aspect of streams, leaving the regular c++ IOStreams library to handle conversion issues. Boost.IOStreams does use exceptions in the way I'd expect. If I understand it correctly, Kenny's example could also look like this:

#include <ostream>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/stream.hpp>

int main () {
  boost::iostreams::stream_buffer <boost::iostreams::file_source> buf("test.txt");
  std::istream file(&buf);

  try {
    std::string buf;
    while (std::getline(file, buf))
      std::cout << "Read> " << buf << "\n";
  }
  catch (std::ios_base::failure::failure e) {
    std::cout << "Exception opening/reading file\n";
  }
  std::cout.flush();

  file.close();

  return 0;
}

I think with this version, things like "file not found" should throw, but 'istream' errors will be reported by badbit/failbit.

Roddy
  • 66,617
  • 42
  • 165
  • 277
3
  1. Whenever you throw an exception you need to think about exception safety. So no exception, no exception, no exception-safety headache.

  2. Iostreams also support exceptions. But throwing an exception is optional. You can enable exception by setting exceptions (failbit | badbit | eofbit)

  3. Iostreams let you entertain both exception and expection-less behavior.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
Neel Basu
  • 12,638
  • 12
  • 82
  • 146
  • 3
    Point 1 is a bit meaningless imho. Without exceptions you have to take care of "error safety" which is much messier in many circumstances than "exception safety" as it's not as neatly codified – Triskeldeian Sep 02 '19 at 15:42
  • No. Throwing an exception causes an abrupt shutdown. Not all resources released properly. The execution goes out of scope. – Neel Basu Sep 02 '19 at 15:46
  • 2
    Throwing doesn't cause an abrupt shutdown. Not catching them does. If you have error codes and you ignore them you could incur in the same trouble and potentially worse one when you continue operating on garbage inputs – Triskeldeian Sep 03 '19 at 15:14
  • It's not guaranteed that the user code would catch all exceptions. But ignoring error codes is not as several as abrupt shutdown. – Neel Basu Sep 03 '19 at 15:18
  • 2
    Exception safety has nothing to do on whether you catch the exceptions or not. It tells you what you should expect from the object that failed once it fails. You could apply the same categories even if you communicate your failure through error codes. – Triskeldeian Sep 03 '19 at 17:37
  • No you cannot apply the same categories if you communicate with the error codes. When you develop a library you don't know whether the user code will catch the exception or not. – Neel Basu Sep 03 '19 at 17:39
  • 2
    Ignoring errors is bad, whether you communicate them through error codes or exceptions. In the case of exceptions if the user does not catch them, the system tells you very clearly that you are doing something very wrong. With error codes it can fail without you noticing until you actually need those data and in my opinion unreliable results are MUCH worse than a crash – Triskeldeian Sep 03 '19 at 17:39
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/198891/discussion-between-triskeldeian-and-neel-basu). – Triskeldeian Sep 03 '19 at 17:40
  • 1
    Apparently you are not reading. Exception safety is not the same thing as exception handling. The first refers to guarantees that you developer give the user about the classes you create, the second refers to the procedures to handle exceptional situation. You talk about exception handling and call it exception safety. – Triskeldeian Sep 03 '19 at 17:55
3

As Kenny says, you can enable exceptions if you want. But normally I/O requires some sort of resumption style of programming when an error occurs, which is not easily supported by using exceptions - testing the state of the stream after an input operation is much simpler. I've never actually seen any C++ code that uses exceptions on I/O.

  • 2
    "some sort of resumption style of programming" - I'm not sure what you mean - I often have stuff like `while(!completed) {try { doIo();completed=true;} catch (...) { if (promptAbortRetry("whoops!") == ABORT) completed = true;}` – Roddy Jul 05 '10 at 14:59
  • 2
    @Roddy By resumption I mean it is sometimes necessary to try to read a value one way, detect failure, and then try to read it another way. This is harder if exceptions are used. –  Jul 05 '10 at 15:12
  • 1
    @Neil - Thanks, makes good sense. To be honest I hadn't considering format conversion exceptions: I was primarily concerned with filesystem-level exceptions (file not found, disk full, whathaveyou) – Roddy Jul 05 '10 at 15:18