返回活动对象的值 [英] Return values for active objects

查看:209
本文介绍了返回活动对象的值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

回到2010年,Herb Sutter提倡在

这里是一个C ++ 11版本:

  class Active {
public:
typedef std :: function< void()>信息;

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; //线程安全并发队列
std :: unique_ptr< std :: thread> thd;

void run(){
while(!done){
消息msg = mq.pop_front();
msg(); //执行消息
} //注意:最后一个消息设置为true
}
};

类可以这样使用:

  class Backgrounder {
public:
void save(std :: string filename){a.send([=] {
// .. 。
}); }

void print(Data& data){a.send([=,& data] {
// ...
}); }

private:
PrivateData somePrivateStateAcrossCalls;
活动a;
};

我想支持非void返回类型的成员函数。但我不能想出一个好的设计如何实现这个,即没有使用可以容纳异构类型的对象(如 boost :: any )的容器。



欢迎任何想法,尤其是使用C ++ 11的功能,如 std :: future std :: promise

解决方案



首先,写 task< Sig> task< Sig> 是一个 std :: function ,只希望其参数可移动

$ <$ p>


$ b c $ c> task< void()> 。所以你可以懒惰,并且你的任务只支持nullary函数。



其次,send创建一个 std :: packaged_task< R> package(f); 。然后它将任务中的未来,然后将移动到消息队列中。 (这就是为什么你只需要一个move-only std :: function ,因为 packed_task 只能移动)。



然后从 packaged_task 中返回 future / p>

  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;
}

客户可以抓取 std :: future



有趣的是,你可以写一个非常简单的move-only nullary任务如下:

 模板< class R> 
struct task {
std :: packaged_task< R>州;
template< class F>
任务(F& f):状态(std :: forward< F>(f)){}
R operator()const {
auto fut = state.get_future ();
state();
return f.get();
}
};

但是这是可笑的低效(打包任务有同步的东西在里面),可能需要一些清理。我觉得很有趣,因为它使用 packed_task 来移动 std :: function 部分。



就我个人而言,我有足够的理由想要只移动任务(在这个问题中)感觉只移动 std :: function 是值得写的。以下是一个这样的实现。它没有被大量优化(可能大约与最小的 std :: function 一样快),而不是调试,但设计是声音:

 模板< 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,我们不关心什么f返回:
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 ...)> > ;
task(task&&)= default;
task& operator =(task&&)= default;
task()= default;
显式运算符bool()const {return static_cast< bool>(pimpl); }

R operator()(Args ... args)const {
return pimpl-> invoke(std :: forward< Args>(args)...)
}
//如果我们可以用签名调用,使用:
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> {}){}

/ /我们是一个void返回类型的情况下,我们不
//关心什么是F的返回类型,只是我们可以调用它:
template< class F,class R2 = R ,class = std :: result_of_t class = std :: enable_if_t< std :: is_same< R2,void> {}
>
task(F& f):task(std :: forward< F>(f),std :: is_convertible< F&,bool> {}){}

/ /这有助于在某些情况下的重载解决:
task(R(* pf)(Args ...)):task(pf,std :: true_type {}){}
// = nullptr支持:
task(std :: nullptr_t):task(){}

private:
//从F构建一个pimpl。 )最终:
template< class F>
任务(F&& f,std :: false_type / *需要一个测试?否!* /):
pimpl(new details_task :: pimpl< R(Args ...),std: :decay_t F> {std :: forward F(f)})
{}
//传入bool,如果有效,构造,否则
//我们应该是空的:
//移动构造,因为我们需要在两个ctors之间的运行时分派。
//如果我们通过测试,dispatch到任务(?false_type)(无需测试)
//如果我们失败了测试,dispatch到task()(空任务)。
template< class F>
任务(F& f,std :: true_type / *需要一个测试?是!* /):
task(f?task(std :: forward& :: false_type {}):task())
{}
};

实例



是库类移动任务对象的第一个草图。它还使用一些C ++ 14东西( std :: blah_t 别名) - 替换 std :: enable_if_t< / code>如果你是一个C ++ 11编译器, typename std :: enable_if<?> :: type

请注意, void 返回类型欺骗包含一些有点问题的模板重载技巧。 (如果在标准的措辞下它是合法的,但每个C ++ 11编译器都会接受它,并且如果不是,它可能变得合法)。


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 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).

这篇关于返回活动对象的值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆