std::bind in Modern C++ 的替代方案

Alternative for std::bind in modern C++

本文关键字:方案 Modern bind in std C++      更新时间:2023-10-16

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

这是它的简化版本。

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绑定的东西。

来自 OP 的评论。

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

这叫埋葬乐德。

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

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

所以这就来了

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;

加入一些互斥锁,摇晃和烘烤。

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

我个人只是给自己写了一个轻量级的动作式std::function<void()>式类,而不是使用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 中,绑定的替换如下所示:

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

在 C++14 中,您可以轻松编写notstd::apply。 移动到 lambda 需要 C++14,因此如果您需要有效地移动参数,则需要 std 绑定或 C++11 中的手动函数对象。

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

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

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

不幸的是,std::bind与lambdas同时进入标准,因此它立即几乎无关紧要。

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

通过这种方式,您不会完全获得完美的转发。捕获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));
});
//.....
}