问题
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
.
回答1:
This will take some work.
First, write task<Sig>
. task<Sig>
is a std::function
that only expects its argument to be movable, not copyable.
Your internal type Messages
are going to be task<void()>
. So you can be lazy and have your task
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 the package
into your queue of messages. (This is why you need a move-only std::function
, because packaged_task
can only be moved).
You then return the future
from the packaged_task
.
template<class F, class R=std::result_of_t<F const&()>>
std::future<R> send(F&& f) {
packaged_task<R> package(std::forward<F>(f));
auto ret = package.get_future();
mq.push_back( std::move(package) );
return ret;
}
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:
template<class R>
struct task {
std::packaged_task<R> state;
template<class F>
task( F&& f ):state(std::forward<F>(f)) {}
R operator()() const {
auto fut = state.get_future();
state();
return f.get();
}
};
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-only std::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 most std::function
however), and not debugged, but the design is sound:
template<class Sig>
struct task;
namespace details_task {
template<class Sig>
struct ipimpl;
template<class R, class...Args>
struct ipimpl<R(Args...)> {
virtual ~ipimpl() {}
virtual R invoke(Args&&...args) const = 0;
};
template<class Sig, class F>
struct pimpl;
template<class R, class...Args, class F>
struct pimpl<R(Args...), F>:ipimpl<R(Args...)> {
F f;
R invoke(Args&&...args) const final override {
return f(std::forward<Args>(args)...);
};
};
// void case, we don't care about what f returns:
template<class...Args, class F>
struct pimpl<void(Args...), F>:ipimpl<void(Args...)> {
F f;
template<class Fin>
pimpl(Fin&&fin):f(std::forward<Fin>(fin)){}
void invoke(Args&&...args) const final override {
f(std::forward<Args>(args)...);
};
};
}
template<class R, class...Args>
struct task<R(Args...)> {
std::unique_ptr< details_task::ipimpl<R(Args...)> > pimpl;
task(task&&)=default;
task&operator=(task&&)=default;
task()=default;
explicit operator bool() const { return static_cast<bool>(pimpl); }
R operator()(Args...args) const {
return pimpl->invoke(std::forward<Args>(args)...);
}
// if we can be called with the signature, use this:
template<class F, class=std::enable_if_t<
std::is_convertible<std::result_of_t<F const&(Args...)>,R>{}
>>
task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// the case where we are a void return type, we don't
// care what the return type of F is, just that we can call it:
template<class F, class R2=R, class=std::result_of_t<F const&(Args...)>,
class=std::enable_if_t<std::is_same<R2, void>{}>
>
task(F&& f):task(std::forward<F>(f), std::is_convertible<F&,bool>{}) {}
// this helps with overload resolution in some cases:
task( R(*pf)(Args...) ):task(pf, std::true_type{}) {}
// = nullptr support:
task( std::nullptr_t ):task() {}
private:
// build a pimpl from F. All ctors get here, or to task() eventually:
template<class F>
task( F&& f, std::false_type /* needs a test? No! */ ):
pimpl( new details_task::pimpl<R(Args...), std::decay_t<F>>{ std::forward<F>(f) } )
{}
// cast incoming to bool, if it works, construct, otherwise
// we should be empty:
// move-constructs, because we need to run-time dispatch between two ctors.
// if we pass the test, dispatch to task(?, false_type) (no test needed)
// if we fail the test, dispatch to task() (empty task).
template<class F>
task( F&& f, std::true_type /* needs a test? Yes! */ ):
task( f?task( std::forward<F>(f), std::false_type{} ):task() )
{}
};
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) -- replace std::enable_if_t<???>
with typename 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).
来源:https://stackoverflow.com/questions/30854070/return-values-for-active-objects