c++ execute function any time a stream is written to

南笙酒味 提交于 2019-12-04 12:58:46

Personally, I wouldn't use an std::ostringstream (or even an std::stringstream) for this at all! Instead, I would create my own stream buffer taking care of sending the data to the GUI. That is, I'd overwrite std::streambuf::overflow() and std::streambuf::sync() to send the current data to the GUI. To also make sure that any output is sent immediately, I'd set up an std::ostream to have std::ios_base::unitbuf set. Actually, sending the changes to a function is quite simple, i.e., I'll implement this:

#include <streambuf>
#include <ostream>
#include <functional>
#include <string>
#include <memory>
#include <iostream> // only for testing...

#if HAS_FUNCTION
typedef std::function<void(std::string)> function_type;
#else
class function_type
{
private:
    struct base {
        virtual ~base() {}
        virtual base* clone() const = 0;
        virtual void  call(std::string const&) = 0;
    };
    template <typename Function>
    struct concrete
        : base {
        Function d_function;
        concrete(Function function)
            : d_function(function) {
        }
        base* clone() const { return new concrete<Function>(this->d_function); }
        void  call(std::string const& value) { this->d_function(value); }
    };
    std::auto_ptr<base> d_function;
public:
    template <typename Function>
    function_type(Function function)
        : d_function(new concrete<Function>(function)) {
    }
    function_type(function_type const& other)
        : d_function(other.d_function->clone()) {
    }
    function_type& operator= (function_type other) {
        this->swap(other);
        return *this;
    }
    ~function_type() {}
    void swap(function_type& other) {
        std::swap(this->d_function, other.d_function);
    }
    void operator()(std::string const& value) {
        this->d_function->call(value);
    }
};
#endif

class functionbuf
    : public std::streambuf {
private:
    typedef std::streambuf::traits_type traits_type;
    function_type d_function;
    char          d_buffer[1024];
    int overflow(int c) {
        if (!traits_type::eq_int_type(c, traits_type::eof())) {
            *this->pptr() = traits_type::to_char_type(c);
            this->pbump(1);
        }
        return this->sync()? traits_type::not_eof(c): traits_type::eof();
    }
    int sync() {
        if (this->pbase() != this->pptr()) {
            this->d_function(std::string(this->pbase(), this->pptr()));
            this->setp(this->pbase(), this->epptr());
        }
        return 0;
    }
public:
    functionbuf(function_type const& function)
        : d_function(function) {
        this->setp(this->d_buffer, this->d_buffer + sizeof(this->d_buffer) - 1);
    }
};

class ofunctionstream
    : private virtual functionbuf
    , public std::ostream {
public:
    ofunctionstream(function_type const& function)
        : functionbuf(function)
        , std::ostream(static_cast<std::streambuf*>(this)) {
        this->flags(std::ios_base::unitbuf);
    }
};

void some_function(std::string const& value) {
    std::cout << "some_function(" << value << ")\n";
}

int main() {
    ofunctionstream out(&some_function);
    out << "hello" << ',' << " world: " << 42 << "\n";
    out << std::nounitbuf << "not" << " as " << "many" << " calls\n" << std::flush;
}

A fair chunk of the above code is actually unrelated to the task at hand: it implements a primitive version of std::function<void(std::string)> in case C++2011 can't be used.

If you don't want quite as many calls, you can turn off std::ios_base::unitbuf and only sent the data upon flushing the stream, e.g. using std::flush (yes, I know about std::endl but it unfortunately is typically misused to I strongly recommend to get rid of it and use std::flush where a flush is really meant).

In order to do this you should create your own streambuf class. streambuf classes represent IO devices and each one takes care of the various issues specific to that kind of device. The standard defines a streambuf for files and another for strings. Network access would use another, and output to a GUI should also be represented as another kind of device if you're going to use streams at all.

Writing an appropriate streambuf class isn't trivial and seems to be kind obscure, but there are resources out there. The C++ Standard Library - A Tutorial and Reference has a small section on this. Standard C++ IOStreams and Locales: Advanced Programmer's Guide and Reference provides in-depth information. A search for subclassing basic_streambuf will also turn up some free resources online.

If you haven't already, can you derive a subclass from stringstream and overload its stream insertion operator to generate events?

Pseudocode:

class AlertingStream : public stringstream
{
    ostream& operator << (type)
    {
        for (each listener in listeners)
        {
            listener.notify();
        }
        perform insertion;
        return *this;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!