Lambda:可能悬空的按引用捕获

7
在《Effective Modern C++》的lambda章节中,Scott Meyers说道:
考虑以下代码:
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;
3个回答

6
由于向量filters的范围超出了函数的范围,因此会出现这种情况。在函数退出时,向量filters仍然存在,并且对divisor的引用被捕获,现在已经失效。

据我所知,filters.emplace_back只有在lambda表达式完成后才返回,在执行过程中,divisor是有效的。

那不是真的。向量存储从闭包创建的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
}

使用OP函数示例,如果我通过using FilterContainer = std::vector<bool>;定义过滤器,那么该引用可能会悬空? - Amadeus
1
@Amadeus 如果他存储了 bool,他就不必在第一时间处理悬空引用,因为所有的 std::vector 中都只包含 true/false 值。但是他存储了返回 boolstd::function,而返回值取决于除数,他需要计算 bool 返回值。 - Andy
@Amadeus 确保向量中存储的内容不存储过时的引用。 - vsoftco

1
除了 @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;
}

实时示例

这个示例在运行时会导致浮点异常,因为当lambda表达式在main中被评估时,对divisor的引用无效。


哦,是真的。我错过了他说“using FilterContainer = std::vector<std :: function<int(int)>>”这部分。我以为它是:“using FilterContainer = std::vector<bool>”。谢谢,你的回答非常有启发性。 - Amadeus

1

在addDivisorFilter处于活动状态时,您没有评估lambda函数。您只是将“该函数”添加到集合中,不知道它何时可能被评估(可能是在addDivisorFilter返回之后很长时间)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接