views:

271

answers:

5

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?

+23  A: 

a. C++ isn'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:

b. 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";
  }
  std::cout.flush();

  file.close();

  return 0;
}
KennyTM
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
`file.close()` - do you need that? I was expecting they were smart enough to close on destruction...???
Roddy
The example is a bit crappy. If you have enabled eof exceptions, why test (incorrectly) for eof?
anon
@Roddy close() will be called by the streams destuctor. However, it's always a good idea to say what you mean explicitly.
anon
@Neil. Thanks - but disagree to explicitly close()ing - it would be like explicitly deleting autoptr objects!
Roddy
The 'history' is interesting, but I thought templates (which are I thought were fundamental to iostreams) were also a very late addition?
Roddy
Originally, the streams weren't templated. That was tacked on after the fact.
Dennis Zickefoose
A: 
  1. Whenever you throw exception you need to think about exception safety. so no exception no exception no exception safety headache.

  2. STL also provides exceptions But throwing exception is optional. you can enable exception by setting exceptions (failbit | badbit | eofbit)

  3. STL lets you entertain both exception and expection-less behavior.

+3  A: 

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.

anon
"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
@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.
anon
@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
A: 

My opinion is that exceptions shouldn't be used for all errors, and shouldn't necessarily be reserved for error handling. The simplest guideline is that exceptions are for handling exceptional cases, where handling the condition otherwise makes a mess of the code with lots of on-exit did-this-fail tests.

In normal I/O handling, things like eof aren't really exceptional - they are expected, and normally more cleanly handled using non-exception methods. Similarly, when searching a container, failure to find the particular key isn't necessarily unexpected or exceptional, so it's appropriate for e.g. std::find to return the end-bound-of-range value for "failure".

On the other hand, while many will disagree, I believe that it's appropriate to use an exception for success in a recursive search. Per call, failure is the normal case - and using an exception for success can clean up the structure of the recursive function considerably. While I'm not sure I would use it in the real world, throwing an exception for reduce and returning only for no-match-found gives a pretty clean and easy-to-follow backtracking LR parser.

BTW - I don't like to rely on iostream for input anyway, except in a simple give-me-the-whole-file-content (or a fixed-size block at a time) way. I find it easier and more robust to use other methods to validate and interpret that input.

Steve314
A: 

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