如何将 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&lt;some_type&gt; 来自对 API A 的调用的情况,但需要向 API B 提供 std::future&lt;void&gt;

std::future<some_type> api_a();

void api_b(std::future<void>& depend_on_this_event);

在没有提议的功能(例如 .then()when_all())的情况下,是否有任何有效的方法来丢弃附加到 std::future&lt;T&gt; 的值并只留下表示事件完成的底层 std::future&lt;void&gt;

类似以下的方法可能有效,但可能效率低下:

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_waiton_timed_wait API 的promise 类型。将 waitwait_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&lt;T&gt;::wait() 返回 void。你测试过你的代码吗? @RalphTandetzky 不,但你在评论之前重新加载了吗?我认为我专门处理了void 案例。啊,我错过了ready_future中的一个,它不涉及wait(),已修复。 @RalphTandetzky 这不是 [stmt.return]/3 中的特殊情况吗? @Yakk future&lt;void&gt; 确实有一个 void get() AFAIK。为什么不使用它? (顺便说一句,恕我直言,future 要求 ready_future_t 专门用于 void,这很烦人。我们对此无能为力吗?) @dyp 我们需要一个调用助手或处理零元参数的东西。遗憾的是,两端都需要它:如果blah() 返回voidready_future&lt;void&gt;( blah() ) 将失败(但如果有任何其他类型则不会)。不确定拥有“更通用”的功能组合会是什么样子。或者什么语言特点。

以上是关于如何将 std::future<T> 转换为 std::future<void>?的主要内容,如果未能解决你的问题,请参考以下文章

std::future<>::get 清空结果

并发 TS:std::future<...>::then,如何在不存储返回的未来的情况下保持链存活?

std::future, std::async, std::promise

std::future, std::async, std::promise

std::future, std::async, std::promise

不完整类型 'class std::future<int> 的无效使用