对于这个例子:
#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 保持活动状态,但没有参考。
您使用的
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)...)
这里
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
上调用任何将释放其状态的成员函数。