Uri London
Uri London

Reputation: 10797

A custom ostream

I need some guidance or pointers understanding how to implement a custom ostream. My requirements are:

  1. A class with a '<<' operator for several data types.
  2. The intention is to send output to database. Each "line" should go to a separate record.
  3. Each record most important field would be the text (or blob), but some other fields such as time, etc. can be mostly deduced automatically
  4. buffering is important, as I don't want to go to database for every record.

First, does it worth deriving from ostream? What do I get by deriving from ostream? What if my class simply implements few operator<< methods (including some custom data types). Which functionality do I get from ostream?

Assuming what I want is a class derived from ostream, I need some guidance understanding the relationship between the ostream and the streambuf classes. Which one do I need to implement? Looking at some samples, it appears that I don't need to derive from ostream at all, and just give the ostream constructor a custom streambuf. Is that true? is that the canonical approach?

Which virtual functions at the custom streambuf do i need to implement? I've seen some samples (including this site: here and here, and few more), some override the sync method, and other override the overflow method. Which one should I override? Also, looking at the stringbuf and filebuf sources (Visual Studio or GCC) both those buffer classes implement many methods of the streambuf.

If a custom class derived from streambuf is required, would there be any benefit deriving from stringbuf (or any other class) instead of directly from streambuf?

As for "lines". I would like at least when my users of the class using the 'endl' manipulator to be a new line (i.e. record in database). Maybe - depends on effort - every '\n' character should be considered as a new record as well. Who do my custom ostream and/or streambuf get notified for each?

Upvotes: 32

Views: 26086

Answers (4)

Uri London
Uri London

Reputation: 10797

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!

Upvotes: 33

zaufi
zaufi

Reputation: 7129

The simplest way is to inherit std::streambuf and override just two methods:

  • std::streamsize xsputn(const char_type* s, std::streamsize n) – to append a given buffer with size provided to your internal buffer, std::string for example;
  • int_type overflow(int_type c) – to append a single char to your internal buffer.

Your streambuf can be constructed from whatever you want (DB connection for example). After append something into the internal buffer you may try to split it into lines and push something into DB (or just buffer an SQL statements to execute later).

To use it: just attach your streambuf to any std::ostream using constructor.

Simple! I've done something like this to output strings to syslog – everything works fine with any custom operator<< for user defined classes.

Upvotes: 26

Bart van Ingen Schenau
Bart van Ingen Schenau

Reputation: 15768

To add a new source or destination of character input/output to the iostreams mechanism, you should create a new streambuf class. The task of the stream buffer classes is to communicate with the 'external device' that will store the characters and to provide buffering facilities.

The problem with using iostreams to communicate with your database is that a database table does not match with the concept of a character sequence. A bit like pushing a round peg in a square hole. A streambuf only operates on characters. That is the only thing ever presented to it. This means the streambuf has to parse the character stream presented to it to find the field and record separators. If you decide to go this route, I predict you will end up writing a CSV-to-SQL converter in your streambuf, just to get it working.

You will probably be better of with just adding a few operator<< overloads to your class(es). You could look at the Qt framework for ideas here. They also have the possibility to use operator<< to add items to a collections and such.

Upvotes: 1

Nim
Nim

Reputation: 33655

my2c - I think you are tackling this the wrong way. A stream may sound like a nice idea, but you'll need a way to indicate the end of the row too (and then what if someone forgets?) I would suggest something along the lines of how the java PreparedStatements and batches work, as in provide a set of methods which accept the types and a column index, then a "batch" method which explicitly makes it clear that you are indeed batching that row and then an execute to push the batch in.

Any stream based operation will rely on type (typically) to indicate which column to fill - but what if you have two ints? IMO, as a user, it doesn't feel like a natural way of inserting records into a database...

Upvotes: 4

Related Questions