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

Pet*_*lev 7 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...));

    //.....
}
Run Code Online (Sandbox Code Playgroud)

还有一些我无法解决的问题.

  1. 有没有办法删除std::bind和存储不同的对象或指针?
  2. 这可能更通用吗?我可以以某种方式存储在单个容器(int,void...)中返回不同对象的函数吗?
  3. 可以在编译时执行创建任务的一些逻辑吗?像consexprbind 这样的东西.

Yak*_*ont 6

来自 OP 的评论。

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

这称为埋葬lede。

如果要存储要在其他线程中调用的可调用对象,则在其他线程中需要签名void()。在线程中,您希望std::future填充 a。

至于绑定参数,虽然有很多std函数会为你做这件事,但我发现最好请求带有预绑定参数的可调用对象。他们可以在外面使用std::bindlambdas 或他们选择的任何其他方式来完成。

那么这就来了

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

std::deque< std::packaged_task<void()> > container;
Run Code Online (Sandbox Code Playgroud)

加入一些互斥体并摇晃和烘烤。

在这里,我将std::packaged_task<void()>带有该签名的任何内容用作预先编写的仅移动类型擦除容器。我们不使用future它可以产生的,这是一种浪费,但它比编写自己的 move-only invoke-once 拥有函数对象更短。

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

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


在结构之外,调用者可以给你任何零参数的可调用对象。

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

将参数的存储放入addTask混合了线程任务队列的责任和参数的混乱。

事实上,我会与线程池分开编写一个线程安全队列,并让线程池使用它:

template<class T>
struct thread_safe_queue;

struct thread_pool {
  thread_safe_queue< std::packaged_task<void()> > queue;
  // etc
};
Run Code Online (Sandbox Code Playgroud)

在 C++17 中,您的绑定的替代品如下所示:

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

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

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

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


Ric*_*ges 6

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

不幸的是std::bind,它与lambda一起成为标准,因此它几乎无关紧要.

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

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

    //.....
}
Run Code Online (Sandbox Code Playgroud)

你不会以这种方式完美转发.捕获中隐含有一个副本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));
                             });

    //.....
}
Run Code Online (Sandbox Code Playgroud)