Why are C++ STL iostreams not "exception friendly"?

21,031

Solution 1

  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";
    }
}

Solution 2

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.

Solution 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.

Share:
21,031
Roddy
Author by

Roddy

Updated on July 09, 2022

Comments

  • Roddy
    Roddy almost 2 years

    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?

  • rubenvb
    rubenvb almost 14 years
    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.
  • Roddy
    Roddy almost 14 years
    file.close() - do you need that? I was expecting they were smart enough to close on destruction...???
  • Admin
    Admin almost 14 years
    The example is a bit crappy. If you have enabled eof exceptions, why test (incorrectly) for eof?
  • Admin
    Admin almost 14 years
    @Roddy close() will be called by the streams destuctor. However, it's always a good idea to say what you mean explicitly.
  • Roddy
    Roddy almost 14 years
    @Neil. Thanks - but disagree to explicitly close()ing - it would be like explicitly deleting autoptr objects!
  • Roddy
    Roddy almost 14 years
    The 'history' is interesting, but I thought templates (which are I thought were fundamental to iostreams) were also a very late addition?
  • Dennis Zickefoose
    Dennis Zickefoose almost 14 years
    Originally, the streams weren't templated. That was tacked on after the fact.
  • Yakov Galka
    Yakov Galka about 12 years
    @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++.
  • LB--
    LB-- about 8 years
    In case anyone is having trouble catching the exception in GCC, it's a bug: gcc.gnu.org/bugzilla/show_bug.cgi?id=66145
  • oli_arborum
    oli_arborum about 6 years
    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.
  • Adriel Jr
    Adriel Jr about 6 years
    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).
  • Triskeldeian
    Triskeldeian over 4 years
    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
  • Neel Basu
    Neel Basu over 4 years
    No. Throwing an exception causes an abrupt shutdown. Not all resources released properly. The execution goes out of scope.
  • Triskeldeian
    Triskeldeian over 4 years
    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
  • Neel Basu
    Neel Basu over 4 years
    It's not guaranteed that the user code would catch all exceptions. But ignoring error codes is not as several as abrupt shutdown.
  • Triskeldeian
    Triskeldeian over 4 years
    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.
  • Neel Basu
    Neel Basu over 4 years
    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.
  • Triskeldeian
    Triskeldeian over 4 years
    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
    Triskeldeian over 4 years
  • Triskeldeian
    Triskeldeian over 4 years
    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.
  • anton_rh
    anton_rh over 3 years
    @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.
  • Yakov Galka
    Yakov Galka over 3 years
    @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.
  • aleck099
    aleck099 about 2 years
    How do you disable it again after the call of exceptions()?