现代C ++中std :: bind的替代方案

时间:2017-09-08 14:16:06

标签: c++ c++11 c++14 c++17

所以我正在尝试创建一个具有不同类型的仿函数容器的类。

这是它的简化版本。

template<class T>
class Container
{

public:
    template<typename F, typename ... ARGS>
    void addTask(F && func, ARGS && ... args);
private:
    std::deque<std::function<T()>> container;

    //.....
};

template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args);
{
    container.emplace_back(std::bind(f,args...));

    //.....
}

我仍然无法解决的问题很少。

  1. 有没有办法删除std::bind并存储不同的对象或指针?
  2. 这可能更通用吗?我可以在一个容器中以某种方式存储返回不同对象的函数(intvoid ...)?
  3. 创建任务的一些逻辑是否可以在编译时执行?像consexpr绑定一样。

2 个答案:

答案 0 :(得分:6)

std::bind来自boost::bind,这在我们有lambda之前是必要的。

不幸的是std::bind与lambda一起成为标准,所以它几乎无关紧要。

在c ++ 14及更高版本中,您可以捕获可变参数lambda中的函数和args:

template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args)
{
    container.emplace_back( [func = std::forward<F>(func),
                             args...] 
                             ()  mutable // make mutable if you want to move the args in to func
                             {
                                 return func(std::move(args)...);
                             });

    //.....
}

你不会以这种方式完美转发。在args...

的捕获中隐含了一个副本

这在c ++ 17中解决了

template<class T>
template<typename F, typename ... ARGS>
T Container<T>::addTask(F && func, ARGS && ... args)
{
    container.emplace_back( [func = std::forward<F>(func),
                             args = std::make_tuple(std::forward<ARGS>(args)...)                                 ] 
                             ()  mutable // make mutable if you want to move the args in to func
                             {
                                 return std::apply(func, std::move(args));
                             });

    //.....
}

答案 1 :(得分:4)

来自OP的评论。

  

有。这是简化的。我在实际代码中使用了期货和一个特殊的容器。它旨在用于多线程环境

这叫做埋葬地狱。

如果要存储要在其他线程中调用的callables,则在另一个线程中需要签名void()。在主题中,您希望填充std::future

对于绑定参数,虽然有许多std函数为您执行此操作,但我发现最好使用预绑定参数请求callables。他们可以在外面使用std::bind或lambdas或他们选择的任何其他方式。

所以这就是

template<class F,
  class R = std::decay_t<std::result_of_t<F&()>>
>
std::future< R >
addTask( F&& func ) {
  auto task = std::packaged_task<R()>(std::forward<F>(f));
  auto ret = task.get_future();
  container.push_back( std::packaged_task<void()>( std::move(task) ) );
  return ret;
}

std::deque< std::packaged_task<void()> > container;
扔掉一些互斥物然后摇一摇。

在这里,我使用std::packaged_task<void()>作为预先写好的仅移动类型擦除容器,用于具有该签名的任何内容。我们不会使用它可以产生的future,这是一种浪费,但它比编写你自己的一次只调用一次拥有的函数对象要短。

我个人刚给自己写了一个轻量级移动std::function<void()> esque类而不是std::packaged_task<void()>,但这可能是不明智的。

调用addTask时,从packaged_task<R()>返回的未来会被填满,这在调用packaged_task<void()>时调用(可能在另一个线程中)。

在结构之外,调用者可以为您提供任何零参数可调用对象。

100次中的99次,简单的[some_arg]{ some_code; }甚至是[]{ some_code; }。在复杂的情况下,他们可以使用更复杂的lambda来处理std::bind或C ++ 14改进。

将参数存储到addTask混合了线程任务队列的责任与参数的混乱。

实际上,我是从线程池中独立编写一个线程安全队列,并让线程池使用它:

template<class T>
struct thread_safe_queue;

struct thread_pool {
  thread_safe_queue< std::packaged_task<void()> > queue;
  // etc
};

在C ++ 17中,对bind的替换如下所示:

[
  func = std::forward<Func>(func),
  args = std::make_tuple( std::forward<Args>(args)... )
]() mutable {
  std::apply( func, std::move(args) );
}

在C ++ 14中,您可以非常轻松地编写notstd::apply。 Move-into-lambda需要C ++ 14,所以如果你需要有效地移动参数,你需要在C ++ 11中使用std bind或手动函数对象。

我认为将参数绑定强烈地绑定在代码的域中使用线程池是最好的。

这也允许线程池执行诸如传递任务额外参数之类的事情,例如&#34;取消令牌&#34;等等。