Back in 2010, Herb Sutter advocated the use of active objects instead of naked threads in an article on Dr. Dobb's.
Here is a C++11 version:
class Active {
public:
typedef std::function<void()> Message;
Active(const Active&) = delete;
void operator=(const Active&) = delete;
Active() : done(false) {
thd = std::unique_ptr<std::thread>(new std::thread( [=]{ this->run(); } ) );
}
~Active() {
send( [&]{ done = true; } );
thd->join();
}
void send(Message m) { mq.push_back(m); }
private:
bool done;
message_queue<Message> mq; // a thread-safe concurrent queue
std::unique_ptr<std::thread> thd;
void run() {
while (!done) {
Message msg = mq.pop_front();
msg(); // execute message
} // note: last message sets done to true
}
};
The class can be used like this:
class Backgrounder {
public:
void save(std::string filename) { a.send( [=] {
// ...
} ); }
void print(Data& data) { a.send( [=, &data] {
// ...
} ); }
private:
PrivateData somePrivateStateAcrossCalls;
Active a;
};
I would like to support member functions with non-void return types. But I cannot come up with a nice design how to implement this, i.e. without using a container that can hold objects of heterogeneous types (like boost::any
).
Any ideas are welcome, especially answers that make use of C++11 features like std::future
and std::promise
.
This will take some work.
First, write
task<Sig>
.task<Sig>
is astd::function
that only expects its argument to be movable, not copyable.Your internal type
Messages
are going to betask<void()>
. So you can be lazy and have yourtask
only support nullary functions if you like.Second, send creates a
std::packaged_task<R> package(f);
. It then gets the future out of the task, and then moves thepackage
into your queue of messages. (This is why you need a move-onlystd::function
, becausepackaged_task
can only be moved).You then return the
future
from thepackaged_task
.clients can grab ahold of the
std::future
and use it to (later) get the result of the call back.Amusingly, you can write a really simple move-only nullary task as follows:
but that is ridiculously inefficient (packaged task has synchronization stuff in it), and probably needs some cleanup. I find it amusing because it uses a
packaged_task
for the move-onlystd::function
part.Personally, I've run into enough reasons to want move-only tasks (among this problem) to feel that a move-only
std::function
is worth writing. What follows is one such implementation. It isn't heavily optimized (probably about as fast as moststd::function
however), and not debugged, but the design is sound:live example.
is a first sketch at a library-class move-only task object. It also uses some C++14 stuff (the
std::blah_t
aliases) -- replacestd::enable_if_t<???>
withtypename std::enable_if<???>::type
if you are a C++11-only compiler.Note that the
void
return type trick contains some marginally questionable template overload tricks. (It is arguable if it is legal under the wording of the standard, but every C++11 compiler will accept it, and it is likely to become legal if it is not).