当传递给"std::async"的变量超出范围时,它仍然有效吗



下面的代码片段合法吗?让我担心的是,当调用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::asyncstd::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通过引用还是通过值获取参数都无关紧要。上述推理没有任何变化。

相关内容

  • 没有找到相关文章

最新更新