ifnull
ifnull

Reputation: 249

Why don't inherit from ifstream

I want to create a custom input file stream which automatically strips comments and other garbage data. I came up with the following solution:

class FileReader : public std::ifstream
{
public:
  explicit FileReader(const char* fName) { open(fName); }

  ~FileReader() { if (is_open()) close(); }

  template <typename T, bool IsBaseOfSerializable>
  struct DoRead
  {
    void operator()(std::ifstream& ifs, T& data) { ifs >> data; }
  };

  template <typename T>
  struct DoRead<T, true>
  {
    void operator()(FileReader& reader, T& data) { data.Deserialize(reader); }
  };

  template <typename T>
  friend FileReader& operator>>(FileReader& reader, T& data)
  {
    reader.SkipCommentsAndGarbage();
    DoRead<T, std::is_base_of<ISerializable, T>::value> doread;
    doread(reader, data);
    return reader;
  }

  void SkipCommentsAndGarbage() {... }
};

I also have interface ISerializable containing Serialize/Deserialize methods. Everything looks fine with me.

But I have read that I should never inherit from std::ifstream and should create custom std::streambuf.

Could you please explain why is it bad to inherit from std::ifstream and how can I create custom std::streambuf which ignores comments and other data in similar way?

Upvotes: 0

Views: 897

Answers (3)

Paul Evans
Paul Evans

Reputation: 27577

It's basically the std::iostream design to customise std::streambuf, that's where all context specific functionality resides.

Upvotes: 1

James Kanze
James Kanze

Reputation: 153929

It's not clear to me how you expect your class to work. The operator>> functions are not virtual in std::istream, and sooner or later (generally sooner, in well written code), you'll end up with a std::istream&. You're idea for forwarding can be used in some cases, but in such cases, you don't inherit from std::ifstream; you contain a pointer to an istream, and forward to it. (By not inheriting, you ensure that you can't end up with a istream&. Which is limiting, but acceptable in certain cases.)

The normal way of doing this is to provide a filtering streambuf, which filters the input text. For example, if comments are from a # to the end of the line, and you don't have to worry about quotes and such, something as simple as the following will work:

class UncommentStreambuf : public std::streambuf
{
    std::streambuf* mySource;
    std::istream*   myOwner;
    char            myBuffer;
protected:
    int underflow() override
    {
        int results = mySource->sbumpc();
        if ( results == '#' ) {
            while ( mySource->sgetc() != '\n' ) {
                mySource->sbumpc();
            }
        }
        if (results != traits_type::eof()) {
            myBuffer = results;
            setg( &myBuffer, &myBuffer, &myBuffer + 1 );
        } else {
            setg( nullptr, nullptr, nullptr );
        }
        return results;
    }
public:
    UncommentStreambuf( std::streambuf* source )
        : mySource( source )
        , myOwner( nullptr )
    {
    }
    UncommentStreambuf( std::istream& source )
        : mySource( source.rdbuf() )
        , myOwner( &source )
    {
        source.rdbuf( this );
    }
    ~UncommentStreambuf()
    {
        if ( myOwner != nullptr ) {
            myOwner->rdbuf( mySource );
        }
    }
};

If you need to handle other types of comments, or worry about quoted comment characters, you'll need more logic (with possibly a private buffer to collect characters, in order to test for a sequence of more than one character).

Upvotes: 4

danielschemmel
danielschemmel

Reputation: 11116

Because publicly inheriting to modify the behavior of ifstream will lead to unintended consequences, because it still is an istream and will be treated as such in many cases - stripping away your custom behavior:

  • When passing FileReader to a function that expects an ::std::istream&, your custom functionality will not be used

  • Any class C with an operator like template<typename T> T& operator>>(T& in, C& target); will be unusable

  • Whenever any functionality of the underlying istream is used directly, your wrapper functions do not work

In your case, you could simply switch to having your ifstream as a member or change its inheritance to private.

Upvotes: 3

Related Questions