C++ Decorate basic_iostream classes

2019-06-22 11:54发布

问题:

I want to do something like the following code shows:

class foo
{
private:
    std::fstream* m_stream;

public:
    foo(std::fstream* stream) : m_stream(stream) { }

    foo& write(char const* s, std::streamsize count)
    {
        if (/*condition*/)
        {
            m_stream->write(s, count);
        }
        else
        {
            // ...
        }

        return *this;
    }

    foo& read(char* s, std::streamsize count)
    {
        if (/*condition*/)
        {
            m_stream->read(s, count);
        }
        else
        {
            // ...
        }

        return *this;
    }
};

I would need to add the same behavior to all similar methods (e.g. put). This shouldn't be applied to file streams only, but all other stream classes. Is there any easy way to allow these functionality?

回答1:

Many of the formatted output operators (operator<<) write directly to the underlying stream buffer. What you need to do in order to accomplish this in a general fashion is derive a class from std::basic_streambuf that forwards all data to another std::basic_streambuf, and then optionally create a minimal std::basic_ostream implementation to make using your stream buffer easier.

I wouldn't say this is particularly easy, though, but it's the only way to do this in a way that can affect all stream types.

Here is an example of a minimal stream buffer that forwards to another stream buffer (and performs some meaningless transformation just to demonstrate what you can do), and an accompanying stream:

#include <iostream>
#include <streambuf>

template<typename CharType, typename Traits = std::char_traits<CharType> >
class ForwardingStreamBuf : public std::basic_streambuf<CharType, Traits>
{
public:
    typedef Traits traits_type;
    typedef typename traits_type::int_type int_type;
    typedef typename traits_type::pos_type pos_type;
    typedef typename traits_type::off_type off_type;

    ForwardingStreamBuf(std::basic_streambuf<CharType, Traits> *baseStreamBuf)
        : _baseStreamBuf(baseStreamBuf)
    {
    }

protected:
    virtual int_type overflow(int_type c = traits_type::eof())
    {
        if( _baseStreamBuf == NULL )
            return traits_type::eof();

        if( traits_type::eq_int_type(c, traits_type::eof()) )
            return traits_type::not_eof(c);
        else
        {
            CharType ch = traits_type::to_char_type(c);
            if( ch >= 'A' && ch <= 'z' )
                ch++; // Do some meaningless transformation
            return _baseStreamBuf->sputc(ch);
        }
    }

    virtual int sync()
    {
        if( _baseStreamBuf == NULL )
            return -1;
        else
            return _baseStreamBuf->pubsync();
    }
private:
    std::basic_streambuf<CharType, Traits> *_baseStreamBuf;
};

template<typename CharType, typename Traits = std::char_traits<CharType> >
class ForwardingStream : public std::basic_ostream<CharType, Traits>
{
public:
    ForwardingStream(std::basic_ostream<CharType, Traits> &stream)
        : std::basic_ostream<CharType, Traits>(NULL), _buffer(stream.rdbuf())
    {
        this->init(&_buffer);
    }

    ForwardingStreamBuf<CharType, Traits>* rdbuf() const
    {
        return &_buffer;
    }
private:
    ForwardingStreamBuf<CharType, Traits> _buffer;
};

This can be used very simply:

int main()
{
    ForwardingStream<char> test(std::cout);
    test << "Foo" << std::endl;
}

Which would output Gpp. I hope that helps you on your way.



回答2:

Something like this?

   template <class Stream>
    class DecoratedStream {
    public:
      DecoratedStream(Stream* stream) : m_stream(stream) {}

      DecoratedStream& write(const char* data, int count) {
        m_stream->write(data, count);
      }

    };


回答3:

If I understand you correctly, you want to decorate methods of any iostream. So just make your decorator take an iostream as decoratee (as opposed to an fstream, which is a subclass of iostream).



回答4:

Having pointer inside a structure as your current approach is dangerous and error prone. Instead just derive such stream classes and implement basic constructors and wrap around your custom methods such as write().

template<typename StreamType>
class foo : StreamType
{
  // wrapper constructors supporting StreamType() constructors
  foo& write(char const* s, std::streamsize count)
  {
    //...
    return *this;
  }
};

Usage:

foo<fstream> obj;
obj.write(...);


回答5:

The usual solution for this sort of problem is to use templates. There aren't that many functions in an std::istream or and std::ostream which need covering, and a good template member for<<and>>should cover a lot of the cases. In most of the cases I've done this, I've only offerred<<or>>`. (Generally speaking, I've not needed bidirectional streams.)

As for handling other types of streams, just use std::iostream instead of std::fstream. (In general, except when opening files, you shouldn't see the fstream part.)