C++11 lambda 无法访问引用

C++11 lambda cannot access reference

本文关键字:访问 引用 lambda C++11      更新时间:2023-10-16

我在C++中遇到lambda函数的问题:我正在尝试定义一个异步加载器,该加载器填充给定字符串列表作为输入的对象数组。

代码看起来像这样(不完全是,但我希望你明白这个想法(:

void loadData() {
    while (we_have_data()) {
        std::string str = getNext();
        array.resize(array.size() + 1);
        element &e = array.back();
        tasks.push_back([&, str] () {
            std::istringstream iss(str);
            iss >> e;
        }
    }
    for (auto task: tasks) {
        task();
    }
}

最后,当我扫描任务列表并执行它们时,应用程序在第一次访问 lambda 中的变量 e 时崩溃。如果我在调试器中运行,我可以在对象 e 本身中找到正确的值。我做错了什么,但我真的不明白是什么。

你拿着一个悬空的引用。 当你这样做时

tasks.push_back([&, str] () {
    std::istringstream iss(str);
    iss >> e;
}

通过引用捕获array.back()返回的元素,因为对e的引用实际上是对e引用的任何内容的引用。 不幸的是,resize是在 while 循环中调用的,因此当调整array大小时,对back()的引用将失效,您现在引用的对象不再存在。

element& e的作用域是while循环。

while 循环的每次迭代之后,您都有 lambda 函数,其中包含对不同e的捕获引用,这些函数都超出了范围。

你捕获e(又名。 创建 lambda 时array.back() ( "by-reference",随后调整array的大小(可能重新分配(,会留下悬而未决的引用,进而在您尝试访问此悬空引用时导致错误。在数组经过调整大小(和重新分配(后,任何尝试(不限于 lambda(通过先前分配的引用访问array中的元素都会导致"悬空引用"问题。

另一种选择...与其使用这两个循环,不如立即在while循环中执行任务,并放弃悬而未决的引用并尝试使基于指针或迭代器的替代方案正常工作。

进一步的替代...如果array中的元素可以共享,则可以使用std::shared_ptr解决方案,但需要注意的是按值捕获shared_ptr元素,从而确保 Lambda 在调整大小时与array共享这些元素的所有权。

这个想法的样本...

void loadData() {
    while (we_have_data()) {
        std::string str = getNext();
        array.resize(array.size() + 1);
        std::shared_ptr<element> e = array.back();
        tasks.push_back([e, str] () {
            std::istringstream iss(str);
            iss >> *e;
        }
    }
    for (auto task: tasks) {
        task();
    }
}

你在这里有两次罢工。

首先,您正在捕获对迭代器的引用,该向量可能会被调整大小并因此重新定位。

其次,您正在捕获对超出范围的局部(堆栈(变量的引用。在循环中,编译器可能每次都对"e"使用相同的内存位置,因此所有引用都将指向相同的堆栈位置。

更简单的解决方案是存储元素编号:

while (we_have_data()) {
    std::string str = getNext();
    size_t e = array.size();
    array.resize(e + 1);
    tasks.push_back([&, e, str] () {
        std::istringstream iss(str);
        iss >> array[e];
    }
}

如果您有 C++14 并且字符串很长,您可能需要考虑:

    tasks.push_back([&, e, str{std::move(str)}] () {

所有这些都假设数组在任务运行时不会经过进一步的操作或超出范围。