Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::ostream that invokes a callback for each line

I am trying to write a custom std::ostream that invokes a function for every line written to it. That is, I would like the following code to work as documented in comments:

my_output_stream s([] (const std::string& line) { 
    WriteLineToSomeOutput(line); 
});

s << "Hello world"; // do not invoke anything. The line has not ended yet!
s << ", from Alex" << std::endl; // here we invoke WriteLineToSomeOutput("hello world, from Alex")
s << "What's up"; // do not invoke anything. The line has not ended yet.
s << ", doc?!\nHow u doing?\n"; // Now we have two lines. We invoke WriteLineToSomeOutput("What's up, doc?!) and WriteLineToSomeOutput("How u doing?")

Note that the data is not written anywhere and not stored anywhere. The only thing that I need the stream to store is the current line that is being aggregated, until we encounter an end of line.

I did not find any easy way of doing so, even when using the boost.Iostreams library. Can I avoid writing my own line tokenizer here by using some built-in facilities of STL and Boost?

Background

The my_output_stream class will be used to adapt between an external library and a logging library used in my application. The external library requires that I provide an std::ostream. And I want to log each line that is logged by the external library using my application's logging framework.

like image 537
Alex Shtof Avatar asked Jul 28 '14 10:07

Alex Shtof


1 Answers

If I understand correctly, you want to unconditionally flush at end of line, and only at end of line. To do this, you must implement your own streambuf; it could be based on std::stringbuf, but if you're only concerned with output, and not worried about seeking, it's probably just as easy to do it yourself.

Something like the following should do the trick:

class LineBufferedOutput : public std::streambuf
{
    std::vector<char> myBuffer;
protected:
    int overflow( int ch ) override
    {
        myBuffer.push_back( ch );
        if ( ch == '\n' ) {
            //   whatever you have to do...
        }
        //  return traits::eof() for failure...
    }
};

I'm not sure what you mean by implementing your own tokenizer; there's no tokenization involved. You do have to look at each character, in order to compare it to '\n', but that's all.

And you ignore any explicit requests to sync().

like image 172
James Kanze Avatar answered Nov 05 '22 22:11

James Kanze