redirect std::cout to a custom writer
Asked Answered
F

5

8

I want to use this snippet from Mr-Edd's iostreams article to print std::clog somewhere.

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>

int main()
{
    std::ostringstream oss;

    // Make clog use the buffer from oss
    std::streambuf *former_buff =
        std::clog.rdbuf(oss.rdbuf());

    std::clog << "This will appear in oss!" << std::flush;

    std::cout << oss.str() << '\\n';

    // Give clog back its previous buffer
    std::clog.rdbuf(former_buff);

    return 0;
}

so, in a mainloop, I will do something like

while (! oss.eof())
{
    //add to window text somewhere
}

Here's the ostringstream docs but I'm having trouble understanding the best way to do this. I have a method that displays the text, I just want to call it with any data in the ostringstream.

What is the easiest/best way to get anything sent to std::clog redirected to a method of my choice? is it as above, and fill in the while !eof part (not sure how), or is there a better way, say by overloading some 'commit' operator somewhere that calls my method? I'm loking for quick and easy, I really don't want to start defining sinks and such with boost iostreams as the article does - that stuff is way over my head.

Fluvial answered 10/2, 2009 at 16:27 Comment(1)
could you be more clear on what your question is?Kenspeckle
I
13

I encourage you to look at Boost.IOStreams. It seems to fit your use-case nicely, and using it is surprisingly simple:

#include <boost/iostreams/concepts.hpp> 
#include <boost/iostreams/stream_buffer.hpp>
#include <iostream>

namespace bio = boost::iostreams;

class MySink : public bio::sink
{
public:
    std::streamsize write(const char* s, std::streamsize n)
    {
        //Do whatever you want with s
        //...
        return n;
    }
};

int main()
{
    bio::stream_buffer<MySink> sb;
    sb.open(MySink());
    std::streambuf * oldbuf = std::clog.rdbuf(&sb);
    std::clog << "hello, world" << std::endl;
    std::clog.rdbuf(oldbuf);
    return 0;
}
Irregular answered 10/2, 2009 at 18:34 Comment(3)
your snippet works in a blank project. i copy-pasted your sink into my real project and copied your main into an init method somewhere, changed NOTHING, and I get several pages of boost nonsense errors.Fluvial
(on this line: bio::stream_buffer<MySink> sb;)Fluvial
I'd love to help, but would need more information about the errors you see. The draconian size limit on the comments does not make it practical here, so I suggest you post your code and the errors it generates on the Boost users list. boost.org/community/groups.html#usersMust
K
8

I think you want to pull the text from the ostream while it's not empty. You could do something like this:

std::string s = oss.str();
if(!s.empty()) {
    // output s here
    oss.str(""); // set oss to contain the empty string
}

Let me know if this isn't what you wanted.

Of course, the better solution is to remove the middle man and have a new streambuf go wherever you really want it, no need to probe later. something like this (note, this does it for every char, but there is plenty of buffering options in streambufs as well):

class outbuf : public std::streambuf {
public:
    outbuf() {
        // no buffering, overflow on every char
        setp(0, 0);
    }

    virtual int_type overflow(int_type c = traits_type::eof()) {
        // add the char to wherever you want it, for example:
        // DebugConsole.setText(DebugControl.text() + c);
        return c;
    }
};

int main() {
    // set std::cout to use my custom streambuf
    outbuf ob;
    std::streambuf *sb = std::cout.rdbuf(&ob);

    // do some work here

    // make sure to restore the original so we don't get a crash on close!
    std::cout.rdbuf(sb);
    return 0;

}

Kenspeckle answered 10/2, 2009 at 16:42 Comment(1)
i didn't expect it to be anywhere near that simple, but i'll give it a try.Fluvial
H
6

I needed to grab outputs to std::cout and std::cerr from third party libraries and log them using log4cxx, and still retaining the original outputs.

This is what I came up with. It's pretty straight-forward:

  • I replace the old buffer of an ostream (like std::cout) with my own class so that I get access to what ever is written to it.

  • I also create a new std::ostream object with the old buffer so that I can continue to get the output to my console, besides sending it to my logger. Which I find kind of handy.

Code:

class intercept_stream : public std::streambuf{
public:
    intercept_stream(std::ostream& stream, char const* logger):
      _logger(log4cxx::Logger::getLogger(logger)),
      _orgstream(stream),
      _newstream(NULL)
    {
        //Swap the the old buffer in ostream with this buffer.
        _orgbuf=_orgstream.rdbuf(this);
        //Create a new ostream that we set the old buffer in
        boost::scoped_ptr<std::ostream> os(new std::ostream(_orgbuf));
        _newstream.swap(os);
    }
    ~intercept_stream(){
        _orgstream.rdbuf(_orgbuf);//Restore old buffer
    }
protected:
    virtual streamsize xsputn(const char *msg, streamsize count){
        //Output to new stream with old buffer (to e.g. screen [std::cout])
        _newstream->write(msg, count);
        //Output to log4cxx logger
        std::string s(msg,count);
        if (_logger->isInfoEnabled()) {
            _logger->forcedLog(::log4cxx::Level::getInfo(), s, LOG4CXX_LOCATION); 
        }
        return count;
    }
private:
    log4cxx::LoggerPtr _logger;
    std::streambuf*    _orgbuf;
    std::ostream&      _orgstream;
    boost::scoped_ptr<std::ostream>  _newstream;
};

Then to use it:

std::cout << "This will just go to my console"<<std::endl;
intercept_stream* intercepter = new intercept_stream(std::cout, "cout");
std::cout << "This will end up in both console and my log4cxx logfile, yay!" << std::endl;
Heathen answered 16/7, 2009 at 11:47 Comment(2)
Could eke out some performance by moving the std::string contruction inside the isInfoEnabled check.Millinery
This works great! I only changed intercept_stream to take a std::string instead of char const* since getLogger takes a string and moved the string construction inside isInfoEnabled check per Rhys Ulerich's suggestion.We
C
1

For the log4cxx example you must override overflow() and sync() otherwise the badbit is always set after first stream is received.

See: http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/fd9d973282e0a402/a872eaedb142debc

InterceptStream::int_type InterceptStream::overflow(int_type c)
{
    if(!traits_type::eq_int_type(c, traits_type::eof()))
    {
        char_type const t = traits_type::to_char_type(c);
        this->xsputn(&t, 1);
    }
    return !traits_type::eof();
}

int InterceptStream::sync()
{
    return 0;
}
Craniology answered 4/1, 2011 at 19:57 Comment(0)
C
0

If you just want to get the contents of the ostringstream, then use its str() member. For example:

string s = oss.str();    
Chandra answered 10/2, 2009 at 16:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.