如何将通用packaged_tasks存储在容器中
How can I store generic packaged_tasks in a container?
我正在尝试以std::async
的方式获取一个"任务"并将其存储在容器中。我必须跳过箍才能实现它,但我认为一定有更好的方法。
std::vector<std::function<void()>> mTasks;
template<class F, class... Args>
std::future<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type>
push(F&& f, Args&&... args)
{
auto func = std::make_shared<std::packaged_task<typename std::result_of<typename std::decay<F>::type(typename std::decay<Args>::type...)>::type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
auto future = func->get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.push_back([=, func = std::move(func)]{ (*func)(); });
return future;
}
所以我正在使用bind
-> packaged_task
-> shared_ptr
-> lambda
-> function
.如何才能更好/更优化地做到这一点?如果有一款std::function
可以承担不可复制但可移动的任务,那肯定会更容易。我可以 std::转发参数到捕获 lambda 中,还是必须使用 bind
?
没有像矫枉过正那样的杀戮。
第 1 步:编写一个 SFINAE 友好的std::result_of
和一个函数来帮助通过元组调用:
namespace details {
template<size_t...Is, class F, class... Args>
auto invoke_tuple( std::index_sequence<Is...>, F&& f, std::tuple<Args>&& args)
{
return std::forward<F>(f)( std::get<Is>(std::move(args)) );
}
// SFINAE friendly result_of:
template<class Invocation, class=void>
struct invoke_result {};
template<class T, class...Args>
struct invoke_result<T(Args...), decltype( void(std::declval<T>()(std::declval<Args>()...)) ) > {
using type = decltype( std::declval<T>()(std::declval<Args>()...) );
};
template<class Invocation, class=void>
struct can_invoke:std::false_type{};
template<class Invocation>
struct can_invoke<Invocation, decltype(void(std::declval<
typename invoke_result<Inocation>::type
>()))>:std::true_type{};
}
template<class F, class... Args>
auto invoke_tuple( F&& f, std::tuple<Args>&& args)
{
return details::invoke_tuple( std::index_sequence_for<Args...>{}, std::forward<F>(f), std::move(args) );
}
// SFINAE friendly result_of:
template<class Invocation>
struct invoke_result:details::invoke_result<Invocation>{};
template<class Invocation>
using invoke_result_t = typename invoke_result<Invocation>::type;
template<class Invocation>
struct can_invoke:details::can_invoke<Invocation>{};
我们现在有invoke_result_t<A(B,C)>
,这是一个SFINAE友好的result_of_t<A(B,C)>
和can_invoke<A(B,C)>
它只是做检查。
接下来,编写一个move_only_function
,一个std::function
的仅移动版本:
namespace details {
template<class Sig>
struct mof_internal;
template<class R, class...Args>
struct mof_internal {
virtual ~mof_internal() {};
// 4 overloads, because I'm insane:
virtual R invoke( Args&&... args ) const& = 0;
virtual R invoke( Args&&... args ) & = 0;
virtual R invoke( Args&&... args ) const&& = 0;
virtual R invoke( Args&&... args ) && = 0;
};
template<class F, class Sig>
struct mof_pimpl;
template<class R, class...Args, class F>
struct mof_pimpl<F, R(Args...)>:mof_internal<R(Args...)> {
F f;
virtual R invoke( Args&&... args ) const& override { return f( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) & override { return f( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) const&& override { return std::move(f)( std::forward<Args>(args)... ); }
virtual R invoke( Args&&... args ) && override { return std::move(f)( std::forward<Args>(args)... ); }
};
}
template<class R, class...Args>
struct move_only_function<R(Args)> {
move_only_function(move_only_function const&)=delete;
move_only_function(move_only_function &&)=default;
move_only_function(std::nullptr_t):move_only_function() {}
move_only_function() = default;
explicit operator bool() const { return pImpl; }
bool operator!() const { return !*this; }
R operator()(Args...args) & { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const& { return pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args) &&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
R operator()(Args...args)const&&{ return std::move(*this).pImpl().invoke(std::forward<Args>(args)...); }
template<class F,class=std::enable_if_t<can_invoke<decay_t<F>(Args...)>>
move_only_function(F&& f):
m_pImpl( std::make_unique<details::mof_pimpl<std::decay_t<F>, R(Args...)>>( std::forward<F>(f) ) )
{}
private:
using internal = details::mof_internal<R(Args...)>;
std::unique_ptr<internal> m_pImpl;
// rvalue helpers:
internal & pImpl() & { return *m_pImpl.get(); }
internal const& pImpl() const& { return *m_pImpl.get(); }
internal && pImpl() && { return std::move(*m_pImpl.get()); }
internal const&& pImpl() const&& { return std::move(*m_pImpl.get()); } // mostly useless
};
没有测试,只是喷出代码。 该can_invoke
为构造函数提供了基本的 SFINAE - 如果您愿意,可以添加"返回类型正确转换"和"void 返回类型意味着我们忽略返回"。
现在我们重新设计您的代码。 首先,您的任务是仅移动函数,而不是函数:
std::vector<move_only_function<X>> mTasks;
接下来,我们将R
类型计算存储一次,然后再次使用它:
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple( std::move(f), std::move(args) );
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back( std::move(task) );
return future;
}
我们将参数填充到一个元组中,将该元组传递到 lambda 中,然后在 lambda 中以"只做一次"的方式调用元组。 由于我们只会调用该函数一次,因此我们针对这种情况优化了 lambda。
packaged_task<R()>
与move_only_function<R()>
兼容,不像std::function<R()>
,所以我们可以将其移动到我们的向量中。 我们从中获得的std::future
应该正常工作,即使我们在move
之前得到了它。
这应该会减少一点开销。 当然,有很多样板。
我没有编译上面的任何代码,我只是把它吐出来,所以它编译的几率很低。 但错误应该主要是 tpyos。
随机地,我决定给move_only_function
4 种不同的()
重载(右值/左值和常量/非重载)。 我本可以添加易变性,但这似乎很鲁莽。 诚然,这增加了样板。
此外,我的move_only_function
缺少std::function
拥有的"获取底层存储内容"操作。 如果您愿意,请随意键入擦除。 它把(R(*)(Args...))0
当作一个真正的函数指针(我在转换为bool
时返回true
,不像 null:转换为 bool
的类型擦除对于更工业质量的实现可能是值得的。
我重写了std::function
std
因为它缺少std::move_only_function
,并且这个概念总体上是一个有用的概念(如packaged_task
所示)。 您的解决方案通过用std::shared_ptr
包装您的可调用对象来使其可移动。
如果您不喜欢上面的样板,请考虑编写 make_copyable(F&&)
,它采用F
函数对象并使用shared_ptr
技术将其包装起来以使其可复制。 您甚至可以添加 SFINAE 以避免在它已经可复制的情况下这样做(并称其为 ensure_copyable
)。
然后你的原始代码会更干净,因为你只是使packaged_task
可复制,然后存储它。
template<class F>
auto make_function_copyable( F&& f ) {
auto sp = std::make_shared<std::decay_t<F>>(std::forward<F>(f));
return [sp](auto&&...args){return (*sp)(std::forward<decltype(args)>(args)...); }
}
template<class F, class... Args, class R=std::result_of_t<std::decay<F>_&&(std::decay_t<Args>&&...)>>
std::future<R>
push(F&& f, Args&&... args)
{
auto tuple_args=std::make_tuple(std::forward<Args>(args)...)];
// lambda will only be called once:
std::packaged_task<R()> task([f=std::forward<F>(f),args=std::move(tuple_args)]
return invoke_tuple( std::move(f), std::move(args) );
});
auto future = func.get_future();
// for some reason I get a compilation error in clang if I get rid of the `=, ` in this capture:
mTasks.emplace_back( make_function_copyable( std::move(task) ) );
return future;
}
这仍然需要上面的invoke_tuple
样板,主要是因为我不喜欢bind
.
- 将字符串存储在c++中的稳定内存中
- std::原子加载和存储都需要吗
- C++:将控制台输出存储在宏中更好吗
- 使用QProcess执行命令,并将结果存储在QStringList中
- 访问存储在向量C++中的结构的多态成员
- 如何从存储在std::映射中的std::集中删除元素
- 存储模板类型以强制转换回派生<T>
- 类型总是使用其大小存储在内存中吗
- 当字符串存储在变量中时,如何将字符串转换为wchar_t
- 使用无符号字符数组有效存储内存
- 如何在cpp.中使用协议缓冲区存储大缓冲区/数组(char/int)
- 使用 pqxx 将 std::vector 存储在 postgresql 中,并从数据库中检索它
- 带结构的二维矢量:如何存储元素
- 添加存储在向量中的大整数的函数出现问题
- 从文件中读取多个字节,并将它们存储在C++中进行比较
- 在std::vector上存储带有模板的类实例
- 谷歌测试中的期望值存储在哪里
- 为什么C中的通用链表中存储的数据已损坏
- 在c++中获取两个大int,并将它们存储在数组中
- 在reactor中存储eventHandlers的最佳方式是什么