Is the code snippet below legal? What worries me is when factorial
is invoked the fut_num
may be already out of scope.
#include <future>
#include <vector>
#include <iostream>
//int factorial(std::future<int> fut) //works, because there is a move constructor
int factorial(std::future<int>&& fut)
{
int res = 1;
int num = fut.get();
for(int i=num; i>1; i--)
{
res *= i;
}
return res;
}
int main()
{
std::promise<int> prs;
std::vector<std::future<int>> vec;
{
std::future<int> fut_num{prs.get_future()};
vec.push_back(std::async(std::launch::async, factorial, std::move(fut_num)));
} //`fut_num` is out of range now.
prs.set_value(5);
for(auto& fut: vec)
{
std::cout << fut.get() << std::endl;
}
}
And the same question about similar code snippet:
#include <future>
#include <vector>
#include <iostream>
//int factorial(std::future<int> fut) //works, because there is a move constructor
int factorial(std::future<int>& fut)
{
int res = 1;
int num = fut.get();
for(int i=num; i>1; i--)
{
res *= i;
}
return res;
}
int main()
{
std::promise<int> prs;
std::vector<std::future<int>> vec;
{
std::future<int> fut_num{prs.get_future()};
vec.push_back(std::async(std::launch::async, factorial, std::ref(fut_num)));
} //`fut_num` is out of range now.
prs.set_value(5);
for(auto& fut: vec)
{
std::cout << fut.get() << std::endl;
}
}
My two cents about these code snippets:
1.The former code snippet is legal, since std::async
copies std::move(fut_num)
(i.e. std::move(fut_num)
is passed by value to std::async
). So there is a local fut_num
when fcatorical
is called.
2.The latter one is illegal, since fut_num
is passed as a reference to std::async
. When fut_num
is out of scope, it's illegal to call functions which uses fut_num
.
CodePudding user response:
The first one is fine, the second one is not.
std::async
with std::launch::async
uses the same procedure of invoking the thread function as the constructor of std::thread
does. Both effectively execute
std::invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...);
in the new thread, but with the auto(...)
constructions executing in the context of the calling thread.
Here F
/Args...
are the (other) template parameters with corresponding forwarding-reference function parameters F&& f
/Args&&... args
and auto
has the new C 23 meaning which creates a prvalue/temporary of the decayed argument's type from the argument.
See [futures.async]/4.1.
This means there will be unnamed copies constructed for all arguments which live in the new thread until after the thread function invocation returns.
So with std::move(fut_num)
, there will actually be another std::future<int>
object which will be move-constructed from fut_num
and live until the thread ends execution. factorial
will be passed a reference to this unnamed object.
With std::ref(fut_num)
you are explicitly by-passing this mechanism protecting you from passing references to the objects in the constructing thread.
The constructor will still make a decayed copy of type std::reference_wrapper<std::future<int>>
from the argument, but that object will just contain a reference referring to fut_num
in the main thread.
std::invoke
will then unwrap the std::reference_wrapper
before passing to factorial
and the fut
argument will refer to fut_num
in the main thread, which is then destroyed without any synchronization, causing undefined behavior as it is also accessed in the factorial
function.
It either case it doesn't matter whether factorial
takes the argument by-reference or by-value. Nothing about the above reasoning changes.