简体   繁体   中英

Lambda: A by-reference capture that could dangle

Scott Meyers, in Effective Modern C++, says, at lambda chapter, that:

Consider the following code:

void addDivisorFilter()
{
    auto calc1 = computeSomeValue1();
    auto calc2 = computeSomeValue2();

    auto divisor = computeDivisor(calc1, calc2);

    filters.emplace_back(
          [&](int value) { return value % divisor == 0; }
    );
}

This code is a problem waiting to happen. The lambda refers to the local variable divisor , but that variable ceases to exist when addDivisorFilter returns. That's immediately after filters.emplace_back returns, so the function that's added to filters is essentially dead on arrival. Using that filter yields undefined behaviour from virtually the moment it's created.

The question is: Why is it an undefined behaviour? For what I understand, filters.emplace_back only returns after lambda expression is complete, and, during it execution, divisor is valid.

Update

An important data that I've missed to include is:

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

That's because the scope of the vector filters outlives the one of the function. At function exit, the vector filters still exists, and the captured reference to divisor is now dangling.

For what I understand, filters.emplace_back only returns after lambda expression is complete, and, during it execution, divisor is valid.

That's not true. The vector stores the lambda created from the closure, and does not "execute" the lambda, you execute the lambda after the function exits. Technically the lambda is constructed from a closure (an compiler-dependent-named class) that uses a reference internally, like

#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
}

You are not evaluating the lambda function while addDivisorFilter is active. You are simply adding "the function" to the collection, not knowing when it might be evaluated (possibly long after addDivisorFilter returned).

In addition to @vsoftco's answer, the following modified example code lets you experience the problem:

#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;
}

live example

This example results in a Floating point exception at runtime, since the reference to divisor is not valid when the lambda is evaluated in main .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM