std::future 执行后是否使 std::async lambda 保持活动状态?

问题描述 投票:0回答:1

对于这个例子:

#include <vector>
#include <future>
#include <memory>
struct S {
  std::string mName = "";
};

std::vector<std::future<void>> futures;
int main(){
  {
    auto s = std::make_shared<S>();
    futures.emplace_back(std::async([s2=s]{}));
  }
  // will s2 be alive here assuming the async execution is done?
}

存储的 future 会让 lambda 保持活动状态,还是 lambda 及其按值捕获会在异步执行完成后被销毁? ChatGPT 说“是”,它会让 lambda 保持活动状态,但没有参考。

c++ lambda std-future
1个回答
0
投票

您使用的

std::async
的声明是根据[futures.async]:

template<class F, class... Args>
  [[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
    async(F&& f, Args&&... args);

首先,传递给

f
的原始 lambda 对象是一个临时对象,将在完整表达式结束时被销毁

futures.emplace_back(std::async([s2=s]{}))

及其捕获

s2
。然而,
std::async
将从 lambda 移动到将被调用的实际对象中。该副本就像由
auto(std::forward<F>(f))
构造的(见下文)。你的问题可能是这个对象什么时候会被销毁,并使用它对
S
对象的最后一个引用。

当您没有为

std::async
提供任何启动策略参数时,它默认为
std::launch::async | std::launch::deferred
,这意味着实现可以选择这两个策略中的任何一个。

如果实现选择

std::launch::async
,那么它的行为就像启动一个执行 ([futures.async]/3.1) 的线程:

invoke(auto(std​::​forward<F>(f)), auto(std​::​forward<Args>(args))...)

在这种情况下,可调用对象的副本是该表达式中的临时对象。一旦返回,它将在调用可调用对象的线程中被销毁。

如果实现选择

std::launch::deferred
,那么它将在
g
的共享状态中存储由
f
初始化的
auto(std​::​forward<F>(f))
的副本
std::future
,并类似地复制由
初始化的
xyz...
args...
 auto(std::forward<Args>(args))...
。当延迟函数被调用时,它应该执行

invoke(std​::​move(g), std​::​move(xyz)...)

参见 [futures.async]/3.2

这里

f
副本的销毁不是表达式或函数延迟调用的一部分。

[futures.async]/3.2 没有进一步指定这些对象何时被销毁。

我认为,鉴于 [futures.async]/3.2 明确说明了

f
的副本何时以及如何存储在共享状态中,并且没有说明它被替换或释放,因此实现不应销毁只是因为延迟调用完成后不再需要它。

std::future::wait
也没有指定它释放未来的共享状态,因此它本身也不应该销毁
g

另一方面,

std::future::get
被指定释放所有共享状态,因此应该销毁
f
的副本。

std::future
移至
futures
也会将存储的状态移至可调用,从而制作 lambda 的另一个副本,但将
S
对象的所有权再次移至新副本。
因此,对于您的具体示例,我认为未指定 

s2

对象在注释行中是否仍然存在。这取决于实现选择哪种策略:如果选择

S
,那么它将被销毁。如果它选择
launch::async
,那么它不会(因为您没有在
launch::deferred
上调用任何将释放其状态的成员函数。
    

© www.soinside.com 2019 - 2024. All rights reserved.