下面的代码片段合法吗?让我担心的是,当调用factorial
时,fut_num
可能已经超出范围。
#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;
}
}
关于类似代码片段的相同问题:
#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;
}
}
我对这些代码片段的两分钱:
1.前一个代码片段是合法的,因为std::async
复制了std::move(fut_num)
(即std::move(fut_num)
通过值传递给std::async
)。因此,当调用fcatorical
时,存在一个本地fut_num
。
2.后一种是非法的,因为fut_num
是作为对std::async
的引用传递的。当fut_num
超出作用域时,调用使用fut_num
的函数是非法的。
第一个很好,第二个不好。
带有std::launch::async
的std::async
使用与std::thread
的构造函数相同的调用线程函数的过程。两者都能有效执行
std::invoke(auto(std::forward<F>(f)), auto(std::forward<Args>(args))...);
但是auto(...)
构造在调用线程的上下文中执行。
这里F
/Args...
是具有相应转发参考函数参数F&& f
/Args&&... args
的(其他)模板参数,并且auto
具有新的C++23含义,其从参数创建衰退参数类型的prvalue/temporary。
参见[futures.async]/4.1。
这意味着在线程函数调用返回之前,将为新线程中的所有参数构建未命名的副本。
因此,对于std::move(fut_num)
,实际上会有另一个std::future<int>
对象,它将从fut_num
移动构建并一直存在,直到线程结束执行。将向factorial
传递对此未命名对象的引用。
使用std::ref(fut_num)
,您可以明确地通过传递此机制来保护您不向构造线程中的对象传递引用。
构造函数仍然会从参数中生成类型std::reference_wrapper<std::future<int>>
的衰退副本,但该对象只包含主线程中引用fut_num
的引用。
然后,std::invoke
将在传递到factorial
之前打开std::reference_wrapper
的包装,并且fut
参数将引用主线程中的fut_num
,然后在没有任何同步的情况下对其进行销毁,导致未定义的行为,因为它也在factorial
函数中被访问。
无论哪种情况,factorial
通过引用还是通过值获取参数都无关紧要。上述推理没有任何变化。