For this example:
#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?
}
will the stored future keep the lambda alive or will that lambda and its by-value captures get destroyed after the async execution is finished? ChatGPT says 'yes' it will keep lambda alive but there is no reference.
The declaration of std::async
that you are using is according to [futures.async]:
template<class F, class... Args>
[[nodiscard]] future<invoke_result_t<decay_t<F>, decay_t<Args>...>>
async(F&& f, Args&&... args);
First of all, the original lambda object passed for f
is a temporary object that will be destroyed at the end of the full-expression
futures.emplace_back(std::async([s2=s]{}))
and its capture s2
with it. However, std::async
will have moved from the lambda into the actual object that will be invoked. This copy is constructed as if by auto(std::forward<F>(f))
(see below). Your question is probably when this object will be destroyed, taking the last reference to the S
object with it.
When you don't give any launch policy argument to std::async
it defaults to std::launch::async | std::launch::deferred
, which means that the implementation can choose either of the two policies. See [futures.async]/3.
If the implementation chooses std::launch::async
, then it shall behave as if starting a thread that executes ([futures.async]/3.1):
invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...)
In this case the copy of the callable is a temporary object in this expression. It will be destroyed in the thread invoking the callable once it returns.
If the implementation chooses std::launch::deferred
, then it shall store a copy g
of f
initialized by auto(std::forward<F>(f))
in the shared state of the std::future
, and similarly copies xyz...
of args...
initialized by auto(std::forward<Args>(args))...
. When the deferred function is invoked, it shall execute
invoke(std::move(g), std::move(xyz)...)
See [futures.async]/3.2.
Here the destruction of the copy of f
is not part of the expression or the deferred invocation of the function.
[futures.async]/3.2 doesn't specify further when these objects are going to be destroyed.
I think, given that [futures.async]/3.2 is clearly stating when and how the copy of f
is stored in the shared state and does neither say that it is replaced or released, the implementation shouldn't destroy it just because it is not needed any more after the deferred invocation finishes.
std::future::wait
also doesn't specify that it releases shared state of the future, so it itself also shouldn't destroy g
.
On the other hand std::future::get
is specified to release all shared state and so should destroy g
.
Moving the std::future
into futures
will also move the state of g
, making another copy of the lambda, but moving the ownership of the S
object by s2
again to the new copy.
So, for your specific example, I think it is unspecified whether or not the S
object will still be alive at the commented line. It depends on which policy the implementation chooses: If it chooses launch::async
, then it will be destroyed, or rather its destruction will happen in the other thread unsynchronized with reaching the comment line. If it chooses launch::deferred
, then it will not be destroyed (because you didn't call any member function on std::future
that will release its state).