Lambda:一个可以悬挂的按引用捕获

Lambda: A by-reference capture that could dangle

本文关键字:引用 一个 Lambda      更新时间:2023-10-16

Scott Meyers在《Effective Modern c++》的lambda章节中说:

考虑以下代码:

void addDivisorFilter()
{
    auto calc1 = computeSomeValue1();
    auto calc2 = computeSomeValue2();
    auto divisor = computeDivisor(calc1, calc2);
    filters.emplace_back(
          [&](int value) { return value % divisor == 0; }
    );
}

这段代码是一个等待发生的问题。lambda指的是局部变量divisor,但是当addDivisorFilter返回时,该变量就不存在了。这是在filters.emplace_back返回后立即发生的,所以添加到filters的功能在到达时基本上是死亡的。使用该过滤器几乎从创建的那一刻起就会产生未定义的行为。

问题是:为什么它是一个未定义的行为?据我所知,filters.emplace_back仅在lambda表达式完成后返回,并且在执行期间,divisor是有效的。

我漏掉了一个重要的数据:

using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;

这是因为向量filters的作用域比函数的作用域更持久。在函数出口,向量filters仍然存在,并且捕获的对divisor的引用现在是悬空的。

据我所知,过滤器。Emplace_back仅在lambda表达式完成后返回,并且在执行期间,除数是有效的。

那不是真的。vector存储从闭包创建的lambda,并且不"执行"lambda,您在函数退出后执行lambda。从技术上讲,lambda是由闭包(依赖于编译器命名的类)构造的,该闭包在内部使用引用,如

#include <vector>
#include <functional>
struct _AnonymousClosure
{
    int& _divisor; // this is what the lambda captures
    bool operator()(int value) { return value % _divisor == 0; }
};
int main()
{
    std::vector<std::function<bool(int)>> filters;
    // local scope
    {
        int divisor = 42;
        filters.emplace_back(_AnonymousClosure{divisor});
    }
    // UB here when using filters, as the reference to divisor dangle
}

当addDivisorFilter处于活动状态时,您没有评估lambda函数。您只是将"函数"添加到集合中,而不知道何时可以对其进行评估(可能在addDivisorFilter返回后很久)。

除了@vsoftco的答案之外,以下修改后的示例代码可以让您体验这个问题:

#include <iostream>
#include <functional>
#include <vector>
void addDivisorFilter(std::vector<std::function<int(int)>>& filters)
{
    int divisor = 5;
    filters.emplace_back(
          [&](int value) { return value % divisor == 0; }
    );
}
int main()
{
    std::vector<std::function<int(int)>> filters;
    addDivisorFilter(filters);
    std::cout << std::boolalpha << filters[0](10) << std::endl;
    return 0;
}

生活例子

这个例子在运行时产生一个Floating point exception,因为当在main中求值lambda时,对divisor的引用是无效的。