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?
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.
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);
}
};
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
).
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(...);
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.)