提问人:Sami 提问时间:7/26/2023 最后编辑:DailyLearnerSami 更新时间:7/28/2023 访问量:53
了解循环中 std::async 中引用的范围和生存期
Understanding Scope and Lifetime of References in std::async within a Loop
问:
我的问题围绕着 listDirs 函数中的 for 循环,我在其中启动异步任务。我通过引用传递路径,然后在单独的线程中调用 listDir 函数。std::async
我知道一旦 for 循环移动到下一次迭代,路径变量(即对路径向量中实例的常量引用)就会超出范围。但是,listDir 函数的参数是应绑定到 path 的引用。std::filesystem::path
我的理解是,即使 path 在 listDirs 函数中超出了范围,但路径向量中的实际实例会在 listDirs 函数的整个持续时间内持续存在,正如我们经过的那样。但我不确定这种理解是否正确。std::filesystem::path
std::ref
有人可以澄清一下这是如何工作的吗?具体说来:
std::ref 是否确保 listDir 获得有效的引用,即使路径超出 listDirs 函数的范围?
在这种情况下,是否存在悬空引用的风险?std::async
#include <filesystem>
using Iterator = std::filesystem::directory_iterator;
// The caller of this function is the thread runtime
std::vector<std::string> listDir(const std::filesystem::path& directory)
{
std::vector<std::string> files;
for (Iterator it(directory); it != Iterator(); ++it)
{
if (it->is_regular_file())
{
files.emplace_back(it->path().filename().string());
}
}
// When we return this vector as the final action in the function, Return Value Optimization(RVO) takes place to
// eliminate any extra copying of the vector
return files;
}
std::vector<std::string> listDirs(const std::vector<std::filesystem::path>& paths)
{
using Iterator = std::filesystem::directory_iterator;
std::vector<std::future<std::vector<std::string>>> futures; // listDir returns std::vector<std::string> type
// iterate over all the directory paths
for (const std::filesystem::path& path : paths)
{
// start each thread using std::async
futures.emplace_back(std::async(listDir, std::ref(path)));
}
std::vector<std::string> allFiles;
for (std::future<std::vector<std::string>>& fut : futures)
{
std::vector<std::string> files = fut.get(); // RVO
std::move(files.begin(), files.end(), std::back_inserter(allFiles));
}
// When we return this vector as the final action in the function, Return Value Optimization(RVO) takes place to
// eliminate any extra copying of the vector
return allFiles;
}
int main()
{
std::filesystem::path currentPath("G:\\lesson4");
std::vector<std::filesystem::path> paths;
for (Iterator it(currentPath); it!= Iterator(); ++it)
{
if (it->is_directory())
{
std::cout << it->path() << '\n';
paths.emplace_back(it->path());
}
}
for (const auto& fileName : listDirs(paths))
{
std::cout << fileName << std::endl;
}
}
答:
在循环中,变量是一个引用。你可以把它想象成一个指针,但事实并非如此。path
for (const std::filesystem::path& path : paths)
{
// start each thread using std::async
futures.emplace_back(std::async(listDir, std::ref(path)));
}
在循环的第一次迭代中,指向量的第一个元素。在第二次迭代中,它引用向量的第二个元素。等等......path
paths
因为在其元素的任何引用(即使是 中使用的元素)的生存期内都不会改变,所以这是安全的。当您使用 时,该引用包装器将封装当前引用。paths
futures
path
std::async
std::ref(path)
事实上,引用包装器通常是使用后台的指针实现的,因为这是将引用作为左值传递的唯一实用方法。
即使在调用第一个异步方法之前循环移动到第二次迭代,引用绑定仍然保持不变,并且仍引用 的第一个元素。paths
评论
path
并没有真正“超出范围”,因为它是对 的元素的引用。这是整个功能的范围。paths
listDirs
path
std::ref(path)
futures
path