How to Create My Own Ostream/Streambuf

How do I create my own ostream/streambuf?

The canonical approach consists in defining your own streambuf.
You should have a look at:

  • Angelika LAnger's articles on IOStreams derivation
  • James Kanze's articles on filtering streambufs
  • boost.iostream for examples of application

A custom ostream

A custom destination for ostream means implementing your own ostreambuf. If you want your streambuf to actually buffer (i.e. don't connect to the database after each character), the easiest way to do that is by creating a class inheriting from std::stringbuf. The only function that you'll need to override is the sync() method, which is being called whenever the stream is flushed.

class MyBuf : public std::stringbuf
{
public:
virtual int sync() {
// add this->str() to database here
// (optionally clear buffer afterwards)
}
};

You can then create a std::ostream using your buffer:

MyBuf buff;
std::ostream stream(&buf)

Most people advised against redirecting the stream to a database, but they ignored my description that the database basically has a single blob field where all text is going to.
In rare cases, I might send data to a different field. This can be facilitated with custom attributes understood by my stream. For example:

MyStream << "Some text " << process_id(1234) << "more text" << std::flush

The code above will create a record in the database with:

blob: 'Some text more text'
process_id: 1234

process_id() is a method returning a structure ProcessID. Then, in the implementation of my ostream, I have an operator<<(ProcessID const& pid), which stores the process ID until it gets written. Works great!

Customize streambuffer for C++ ostream

The problem, it seems, is that your object d is destroyed before std::cout, and thus the final calls for destructing the global object, which include flushing buffers, and that take palce after the end of main() (remember it's a global object), attempt to perform operations on a no longer-extant streambuf object. Your buffer object definitely should outlive the stream you associate it with.

One way of having this in you program is to make d into a pointer, which you will never delete. Alternatively, you can keep your local object as you used it, but call std::cout.flush(), and then assign cout's buffer to something else (even nullptr) before going out of scope.

While testing with your program (and before I found the problem), I made small changes that made sense to me. For example, after you successfully write to the descriptor, you can simply bump(ret) (you already know that ret!=-1, so its safe to use).

Other changes that I didn't make, but which you could consider, are to have the descriptor set by the constructor itself, having the destructor close a dangling descriptor, and perhaps change dynamic allocation from C-oriented malloc()/realloc()/free() to C++-oriented std::vector.

Speaking of allocation, you made a very common mistake when using realloc(). If the reallocation fails, realloc() will keep the original pointer intact, and signal the failure by returning a null pointer. Since you use the same pointer to get the return value, you risk losing the reference to a still allocated memory. So, if you at all cannot use C++ containers instead of C pointers, you should change you code to something more like this:

char *newptr;
newptr=static_cast<char *>(realloc(ptr, newsize));
if(newptr)
ptr=newptr;
else {
// Any treatment you want. I wrote some fatal failure code, but
// you might even prefer to go on with current buffer.
perror("ralloc()");
exit(1);
}

creating an ostream

As it is for education, as you say, i will show you how i would do such a thingy. Otherwise, stringstream is really the way to go.

Sounds like you want to create a streambuf implementation that then writes to a vector / deque. Something like this (copying from another answer of me that targeted a /dev/null stream):

template<typename Ch, typename Traits = std::char_traits<Ch>,
typename Sequence = std::vector<Ch> >
struct basic_seqbuf : std::basic_streambuf<Ch, Traits> {
typedef std::basic_streambuf<Ch, Traits> base_type;
typedef typename base_type::int_type int_type;
typedef typename base_type::traits_type traits_type;

virtual int_type overflow(int_type ch) {
if(traits_type::eq_int_type(ch, traits_type::eof()))
return traits_type::eof();
c.push_back(traits_type::to_char_type(ch));
return ch;
}

Sequence const& get_sequence() const {
return c;
}
protected:
Sequence c;
};

// convenient typedefs
typedef basic_seqbuf<char> seqbuf;
typedef basic_seqbuf<wchar_t> wseqbuf;

You can use it like this:

seqbuf s;
std::ostream os(&s);
os << "hello, i'm " << 22 << " years old" << std::endl;
std::vector<char> v = s.get_sequence();

If you want to have a deque as sequence, you can do so:

typedef basic_seqbuf< char, char_traits<char>, std::deque<char> > dseq_buf;

Or something similar... Well i haven't tested it. But maybe that's also a good thing - so if it contains still bugs, you can try fixing them.

Wrapping FILE* with custom std::ostream

As Ben Voigt points out, you want to subclass streambuf. There are pages on the University of Southern California's website which have the documentation, header, and source for a GNU implementation of a streambuf subclass (stdiobuf) that wraps a FILE*. It has some dependencies on the library it is a part of (GroovX), but those should be easily to remove (I would begin by removing all references to GVX_TRACE).

Interestingly, it also provides a minimalistic subclass (stdiostream) of std::iostream, in spite of what Ben Voigt said. But this does not seem to be necessary, as the rdbuf ("read buffer"/set the stream buffer) method which the stdiostream class uses to connect the stdiobuf class to a stream object is publicly accessible.

You can find more about subclassing streambuf here (look particularly at the bottom of the page, which discussing the virtual functions). The implementation linked above overrides sync, underflow (to support input) and overflow (to support output).

Further notes about the linked implementation:

  • The init method uses the setg and setp methods to set the pointers for the input and output sequences.
  • The line const int num = pptr()-pbase(); is calculating the number of characters to flush by subtracting the base output pointer from the current output pointer ("put pointer").
  • The variable unhelpfully named om is the mode parameter.
  • The variable named fd is the file descriptor.

Custom streambuffer in std::ofstream

The concrete file stream classes have their own rdbuf() method that takes 0 arguments and it hides the other rdbuf() method inherited from the virtual base std::basic_ios. Qualifying the name to lookup the base class method should work:

std::ofstream ofs;
ofs.basic_ios<char>::rdbuf(example.rdbuf());

Have a C++ Class act like a custom ostream, sstream

try this:

class MyObject {
public:
template <class T>
MyObject &operator<<(const T &x) {
s << ':' << x << ':';
return *this;
}

std::string to_string() const { return s.str(); }

private:
std::ostringstream s;
};

MyObject obj;
obj << "Hello" << 12345;
std::cout << obj.to_string() << std::endl;

There are certain things you won't be able to shove into the stream, but it should work for all the basics.

How to write custom input stream in C++

The proper way to create a new stream in C++ is to derive from std::streambuf and to override the underflow() operation for reading and the overflow() and sync() operations for writing. For your purpose you'd create a filtering stream buffer which takes another stream buffer (and possibly a stream from which the stream buffer can be extracted using rdbuf()) as argument and implements its own operations in terms of this stream buffer.

The basic outline of a stream buffer would be something like this:

class compressbuf
: public std::streambuf {
std::streambuf* sbuf_;
char* buffer_;
// context for the compression
public:
compressbuf(std::streambuf* sbuf)
: sbuf_(sbuf), buffer_(new char[1024]) {
// initialize compression context
}
~compressbuf() { delete[] this->buffer_; }
int underflow() {
if (this->gptr() == this->egptr()) {
// decompress data into buffer_, obtaining its own input from
// this->sbuf_; if necessary resize buffer
// the next statement assumes "size" characters were produced (if
// no more characters are available, size == 0.
this->setg(this->buffer_, this->buffer_, this->buffer_ + size);
}
return this->gptr() == this->egptr()
? std::char_traits<char>::eof()
: std::char_traits<char>::to_int_type(*this->gptr());
}
};

How underflow() looks exactly depends on the compression library being used. Most libraries I have used keep an internal buffer which needs to be filled and which retains the bytes which are not yet consumed. Typically, it is fairly easy to hook the decompression into underflow().

Once the stream buffer is created, you can just initialize an std::istream object with the stream buffer:

std::ifstream fin("some.file");
compressbuf sbuf(fin.rdbuf());
std::istream in(&sbuf);

If you are going to use the stream buffer frequently, you might want to encapsulate the object construction into a class, e.g., icompressstream. Doing so is a bit tricky because the base class std::ios is a virtual base and is the actual location where the stream buffer is stored. To construct the stream buffer before passing a pointer to a std::ios thus requires jumping through a few hoops: It requires the use of a virtual base class. Here is how this could look roughly:

struct compressstream_base {
compressbuf sbuf_;
compressstream_base(std::streambuf* sbuf): sbuf_(sbuf) {}
};
class icompressstream
: virtual compressstream_base
, public std::istream {
public:
icompressstream(std::streambuf* sbuf)
: compressstream_base(sbuf)
, std::ios(&this->sbuf_)
, std::istream(&this->sbuf_) {
}
};

(I just typed this code without a simple way to test that it is reasonably correct; please expect typos but the overall approach should work as described)



Related Topics



Leave a reply



Submit