Lambdas 和通过引用局部变量捕获:在作用域之后访问

2021-12-23 00:00:00 lambda local c++ c++11

我通过引用两个 lambda 来传递我的本地变量.我在函数作用域之外调用这些 lambda.这是 undefined 吗?

I am passing my local-variables by reference to two lambda. I call these lambdas outside of the function scope. Is this undefined ?

std::pair<std::function<int()>, std::function<int()>> addSome() {
    int a = 0, b = 0;
    return std::make_pair([&a,&b] {
        ++a; ++b;
        return a+b;
        }, [&a, &b] {
            return a;
        });
}

int main() {
    auto f = addSome();
    std::cout << f.first() << " " << f.second();
    return 0;
}

如果不是,则一个 lambda 的变化不会反映在另一个 lambda 中.

If it is not, however, changes in one lambda are not reflected in other lambda.

我是否误解了 lambda 上下文中的引用传递?

Am i misunderstanding pass-by-reference in context of lambdas ?

我正在写入变量,它似乎工作正常,输出没有运行时错误

I am writing to the variables and it seems to be working fine with no runtime-errors with output

2 0.如果它有效,那么我希望输出 2 1.

2 0. If it works then i would expect output 2 1.

推荐答案

是的,这会导致未定义的行为.lambda 将引用超出范围的堆栈分配对象.(从技术上讲,据我所知,行为是定义的,直到 lambdas 访问 a 和/或 b.如果您从不调用返回的 lambdas,则没有 UB.)

Yes, this causes undefined behavior. The lambdas will reference stack-allocated objects that have gone out of scope. (Technically, as I understand it, the behavior is defined until the lambdas access a and/or b. If you never invoke the returned lambdas then there is no UB.)

这是未定义的行为,与返回对堆栈分配的本地的引用然后在本地超出范围后使用该引用的未定义行为相同,除了在这种情况下它被 lambda 混淆了一点.

This is undefined behavior the same way that it's undefined behavior to return a reference to a stack-allocated local and then use that reference after the local goes out of scope, except that in this case it's being obfuscated a bit by the lambda.

此外,请注意未指定 lambda 表达式的调用顺序――编译器可以在 f.first() 之前自由调用 f.second()因为两者都是同一个完整表达式的一部分.因此,即使我们修复了由于使用对已销毁对象的引用而导致的未定义行为,2 02 1 仍然是此操作的仍然有效输出程序,你得到的取决于你的编译器决定执行 lambdas 的顺序.请注意,这不是未定义的行为,因为编译器不能做任何事情,而只是在决定执行顺序方面有一些自由一些事情.

Further, note that the order in which the lambdas are invoked is unspecified -- the compiler is free to invoke f.second() before f.first() because both are part of the same full-expression. Therefore, even if we fix the undefined behavior caused by using references to destroyed objects, both 2 0 and 2 1 are still valid outputs from this program, and which you get depends on the order in which your compiler decides to execute the lambdas. Note that this is not undefined behavior, because the compiler can't do anything at all, rather it simply has some freedom in deciding the order in which to do some things.

(请记住,您的 main() 函数中的 << 正在调用自定义的 operator<< 函数,并且函数参数的计算顺序是未指定的.编译器可以自由地发出代码,以任何顺序计算同一完整表达式中的所有函数参数,约束条件是必须在该函数之前计算函数的所有参数调用.)

(Keep in mind that << in your main() function is invoking a custom operator<< function, and the order in which function arguments are evaluated is unspecified. Compilers are free to emit code that evaluates all of the function arguments within the same full-expression in any order, with the constraint that all arguments to a function must be evaluated before that function is invoked.)

要解决第一个问题,请使用 std::shared_ptr 创建引用计数对象.按值捕获这个共享指针,只要它们(及其任何副本)存在,lambda 就会使指向的对象保持活动状态.这个堆分配的对象是我们将存储 ab 的共享状态的地方.

To fix the first problem, use std::shared_ptr to create a reference-counted object. Capture this shared pointer by value, and the lambdas will keep the pointed-to object alive as long as they (and any copies thereof) exist. This heap-allocated object is where we will store the shared state of a and b.

要解决第二个问题,请在单独的语句中评估每个 lambda.

To fix the second problem, evaluate each lambda in a separate statement.

这是你的代码改写了未定义的行为,并且保证在 f.second() 之前调用 f.first() :

Here is your code rewritten with the undefined behavior fixed, and with f.first() guaranteed to be invoked before f.second():

std::pair<std::function<int()>, std::function<int()>> addSome() {
    // We store the "a" and "b" ints instead in a shared_ptr containing a pair.
    auto numbers = std::make_shared<std::pair<int, int>>(0, 0);

    // a becomes numbers->first
    // b becomes numbers->second

    // And we capture the shared_ptr by value.
    return std::make_pair(
        [numbers] {
            ++numbers->first;
            ++numbers->second;
            return numbers->first + numbers->second;
        },
        [numbers] {
            return numbers->first;
        }
    );
}

int main() {
    auto f = addSome();
    // We break apart the output into two statements to guarantee that f.first()
    // is evaluated prior to f.second().
    std::cout << f.first();
    std::cout << " " << f.second();
    return 0;
}

(看它运行.)

相关文章