如何将 std::future<T> 转换为 std::future<void>?
Posted
技术标签:
【中文标题】如何将 std::future<T> 转换为 std::future<void>?【英文标题】:How to convert std::future<T> to std::future<void>? 【发布时间】:2015-04-13 22:28:33 【问题描述】:我有一个std::future<some_type>
来自对 API A 的调用的情况,但需要向 API B 提供 std::future<void>
:
std::future<some_type> api_a();
void api_b(std::future<void>& depend_on_this_event);
在没有提议的功能(例如 .then()
或 when_all()
)的情况下,是否有任何有效的方法来丢弃附加到 std::future<T>
的值并只留下表示事件完成的底层 std::future<void>
?
类似以下的方法可能有效,但可能效率低下:
auto f = api_a();
f.wait();
auto void_f = std::async(std::launch::defer, []);
api_b(void_f);
【问题讨论】:
您自己想出了唯一的解决方案。这就是 C++ 如此需要future.then
的原因。同时,请考虑 boost.threads,它对可组合期货具有实验性支持。
谢谢。同意future.then
在功能上是正确的。但是,我认为对于上述用例来说,它可能仍然比必要的成本更高——我只想将现有的未来“投射”到void
。理想情况下,这不需要排队继续。
比.then
更好的是带有on_wait
和on_timed_wait
API 的promise
类型。将 wait
和 wait_for
函数转发到“包装的”std::future
会很容易,并且在创建时不需要同步。
【参考方案1】:
你能得到的最好的可能是这样的:
auto f = api_a();
auto void_f = std::async(std::launch::deferred,[fut = std::move(f)] fut.wait(););
api_b(void_f);
【讨论】:
不利的一面是,在您wait()
之前,上述内容永远不会是 ready()
。即使是定时等待也永远不会完成。因此,您必须审核消费代码以确保它们可以使用延迟期货,因为它们的行为与实际中的异步期货不同。
这可以通过省略std::launch::defer
来避免,但代价是可能会启动另一个线程并导致性能下降。【参考方案2】:
template<class U>
struct convert_future_t
template<class T>
std::future<U> operator()( std::future<T>&& f ) const
return std::async(std::launch::deferred,
[f=std::move(f)]()->U return f.get();
);
template<>
struct convert_future_t<void>
template<class T>
std::future<void> operator()( std::future<T>&& f ) const
return std::async(std::launch::deferred,
[f=std::move(f)]()->void f.get();
);
template<class U, class T>
std::future<U> convert_future( std::future<T>&& f )
return convert_future_t<U>(std::move(f));
这是@sbabbi 答案的通用版本。
api_b( convert_future<void>( api_a() ) );
允许任何目标和目标类型透明地工作。
这种方法的一大缺点是,产生的 future 是一个包含(可能是异步的)future 的延迟future,这意味着.wait_for()
和.ready()
API 不像异步future 那样工作。返回的 future 在等待之前永远不会准备好。
所以我们可以稍微改进一下:
template<class T>
struct ready_future_t
template<class...Us>
std::future<T> operator()( Us&&...us ) const
std::promise<T> p;
p.set_value(T(std::forward<Us>(us)...));
return p.get_future();
;
template<>
struct ready_future_t<void>
using T=void;
// throws away the Us&&...s
template<class...Us>
std::future<T> operator()( Us&&...us ) const
std::promise<T> p;
p.set_value();
return p.get_future();
;
template<class T, class...Us>
std::future<T> ready_future(Us&&...us)
return ready_future_t<T>(std::forward<Us>(us)...);
template<class U>
struct convert_future_t
template<class T>
std::future<U> operator()( std::future<T>&& f ) const
if (f.wait_for(0ms)==std::future_status::ready)
return ready_future<U>(f.get());
return std::async(std::launch::deferred,
[f=std::move(f)]()->U return f.get();
);
;
template<>
struct convert_future_t<void>
template<class T>
std::future<void> operator()( std::future<T>&& f ) const
if (f.wait_for(0ms)==std::future_status::ready)
return ready_future<void>();
return std::async(std::launch::deferred,
[f=std::move(f)]()->void f.get();
);
;
至少如果未来在我们转换它时已经准备好了,那么返回的未来也准备好了。
live example
【讨论】:
return f.wait()
看起来不对,因为 std::future<T>::wait()
返回 void。你测试过你的代码吗?
@RalphTandetzky 不,但你在评论之前重新加载了吗?我认为我专门处理了void
案例。啊,我错过了ready_future
中的一个,它不涉及wait()
,已修复。
@RalphTandetzky 这不是 [stmt.return]/3 中的特殊情况吗?
@Yakk future<void>
确实有一个 void get()
AFAIK。为什么不使用它? (顺便说一句,恕我直言,future
要求 ready_future_t
专门用于 void
,这很烦人。我们对此无能为力吗?)
@dyp 我们需要一个调用助手或处理零元参数的东西。遗憾的是,两端都需要它:如果blah()
返回void
,ready_future<void>( blah() )
将失败(但如果有任何其他类型则不会)。不确定拥有“更通用”的功能组合会是什么样子。或者什么语言特点。以上是关于如何将 std::future<T> 转换为 std::future<void>?的主要内容,如果未能解决你的问题,请参考以下文章
并发 TS:std::future<...>::then,如何在不存储返回的未来的情况下保持链存活?
std::future, std::async, std::promise
std::future, std::async, std::promise