samedi 30 janvier 2016

wrapper class for ostream to dispatch stream operator<< calls through a strand

I am fairly new to boost::asio and I am trying to implement a class to wrap an std::ostream object so that all calls to the stream operator are serialized through a strand.

Ideally, this is the behavior i'm looking for:

// from thread 1
logstream << "Kiwi";
// from thread 2
logstream << "Apple";

//logstream output could be "KiwiApple" or "AppleKiwi", but
// never something like "KAppleiwi"

I have been working from other answers (here and here) explaining how to wrap an ostream to get custom behavior, but it relies on a template argument which I can't get to play nice when I use it inside an anonymous handler passed to strand::dispatch(...). I should also add that I have dropped the const modifiers for the time being as they were causing more problems (I could also use some advice on this). Here is my code:

// logstream.hpp ------------------------------------------------------------
#include <ostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

class LogStream {
public:
    // construct with an io service and a unique ostream
    LogStream(std::shared_ptr<boost::asio::io_service> io_service,
        std::ostream &out)
        : work_(*io_service), strand_(*io_service), out_(out) {};

    // used to be:
    // template<typename T>
    // inline const LogStream & operator<<(const T& v) const {...}
    template<typename T>
    inline LogStream& operator<<( T& v)  {
        // ideally i would like to dispatch an anonymous "quickie"...
        strand_.dispatch([this, v](){this->out_ << v; });
        return *this;
    };

private:
    boost::asio::io_service::work work_;
    boost::asio::strand strand_;
    std::ostream &out_;
    // no default constructor
    LogStream() = delete;
};

// main.cpp -----------------------------------------------------------------
#include <iostream>
#include <logger/logstream.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>

using namespace std;

void foo(int id, LogStream &ls) {
    for (int i = 0; i < 1000; i++) {
        // ls << i; // this actually seems to work
        ls << "kiwi"; // compiler error - it doesn't like the array of characters.
    }
}

void worker(std::shared_ptr<boost::asio::io_service> io_service) {
    io_service->run();
}

int main() {
    std::shared_ptr<boost::asio::io_service> io_service = 
        std::shared_ptr<boost::asio::io_service>(new boost::asio::io_service());

    LogStream ls = LogStream(io_service, cout);

    boost::thread_group workers;
    for (int i = 0; i < 4; i++) {
        workers.create_thread(boost::bind(&worker, io_service));
    }

    boost::thread_group threads;
    for (int i = 0; i < 4; i++) {
        threads.create_thread(boost::bind(&foo, i, ls));
    }
    io_service->run();


    return 0;
}

I have tried a couple different variations of this basic structure (using boost::bind with a class method, for example) and the errors are different every time. As I presented it, the code now generates this compiler error:

error C2536: 'LogStream::<<::<lambda_d3e85e85f4366eae12cc7bf533b39faa>::LogStream::<<::<lambda_d3e85e85f4366eae12cc7bf533b39faa>::v' : cannot specify explicit initializer for arrays
1>          d:\code projects\wfc\cpp\src\logger\logstream.hpp(28) : see declaration of 'LogStream::<<::<lambda_d3e85e85f4366eae12cc7bf533b39faa>::v'

I have looked around for info on error C2536, but I am having a hard time relating those answers to my code. As I mentioned before, I am new to boost, and I am also not very experienced with using templates.

Answers that explain what I am doing wrong, and answers that suggest a better way to achieve the desired behavior are equally welcome. Thanks very much in advance for any help.

I am compiling with msvc120 (2013).

Aucun commentaire:

Enregistrer un commentaire