vendredi 25 septembre 2015

How do I give streams better exception messages?

The problem

As of right now, the exception support for streams are terrible. When the Boost.System library was adopted into C++11, one was given the impression that maybe exceptions would improve. All the change did was replace std::exception with std::system_error. While <system_error> is a good library on its own for developers, the standard committee and standard library implementors have not taken any steps towards using it to improve exception messages.

To give an idea of how terrible it is, here's a brief summary of what goes down:

  • An error occurs.

  • setstate is used to set badbit or failbit.

  • clear is called by setstate.

  • If exceptions are enabled, clear will throw an ios_base::failure.

Yes, that means for ALL errors the same useless exception message is thrown. This is specified at the basic_ios level, so all derived classes suffer from this issue. The offending quote:

[iostate.flags]/4 Effects: If ((state | (rdbuf() ? goodbit : badbit)) & exceptions()) == 0, returns. Otherwise, the function throws an object fail of class basic_ios::failure (27.5.3.1.1), constructed with implementation-defined argument values.

Here's an example of what "implementation-defined argument values" gives us:

ios_base::clear: unspecified iostream_category error

Is there an easy fix?

Neither Boost.Filesystem nor Boost.Iostreams are replacements for <iostream>. The former is a library for portably dealing with a filesystem (and is likely to appear in the next revision of C++) while the latter has something to do with..Sources and Sinks. The documentation states that it delegates exceptions to ios_base::failure anyways. Boost.Filesystem does provide <boost/filesystem/fstream.hpp> which uses path instead of const char* arguments to open(). It shows an example of how one might inherit from standard library classes:

  template < class charT, class traits = std::char_traits<charT> >
  class basic_ifstream : public std::basic_ifstream<charT,traits>
  {
  private: // disallow copying
    basic_ifstream(const basic_ifstream&);
    const basic_ifstream& operator=(const basic_ifstream&);

  public:
    basic_ifstream() {}

    // use two signatures, rather than one signature with default second
    // argument, to workaround VC++ 7.1 bug (ID VSWhidbey 38416)

    explicit basic_ifstream(const path& p)
      : std::basic_ifstream<charT,traits>(p.BOOST_FILESYSTEM_C_STR, std::ios_base::in) {}

    basic_ifstream(const path& p, std::ios_base::openmode mode)
      : std::basic_ifstream<charT,traits>(p.BOOST_FILESYSTEM_C_STR, mode) {}

    void open(const path& p)
      { std::basic_ifstream<charT,traits>::open(p.BOOST_FILESYSTEM_C_STR, std::ios_base::in); }

    void open(const path& p, std::ios_base::openmode mode)
      { std::basic_ifstream<charT,traits>::open(p.BOOST_FILESYSTEM_C_STR, mode); }

    virtual ~basic_ifstream() {}
  };

This is a neat trick, except since our offending function is non-virtual and all the way up in basic_ios, there's a combinatorial explosion of what we have to reimplement:

http://ift.tt/1KVQk1S. png

I can't post images, so remove the space.

I suspect an entire rewrite is needed because simply replacing clear() won't be enough. The stream can fail for multiple reasons but there's only one type of exception thrown. While std::system_error gives us better tools of expressing errors, that doesn't help if, again, there's no way to distinguish the source of the error.

However I'm not a library writer and don't feel like taking on this task. Are there any other options than the ones I listed?

Aucun commentaire:

Enregistrer un commentaire