所以我正在尝试创建一个具有不同类型的仿函数容器的类。
这是它的简化版本。
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...));
//.....
}
我仍然无法解决的问题很少。
std::bind
并存储不同的对象或指针?int
,void
...)?consexpr
绑定一样。答案 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;等等。