Lambdas并通过引用捕获局部变量:在范围之后访问 [英] Lambdas and capture by reference local variables : Accessing after the scope

查看:122
本文介绍了Lambdas并通过引用捕获局部变量:在范围之后访问的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我通过引用两个lambda传递我的局部变量。我把这些lambdas称为函数范围之外。是 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。



在lambdas的上下文中我误解了pass-by-reference?



变量,它似乎工作正常与没有运行时错误与输出



2 0 。如果它工作,我会期望输出 2 1

解决方案

是,这会导致未定义的行为。 lambdas将引用超出范围的堆栈分配的对象。 (技术上,据我理解,行为被定义,直到lambdas访问 a 和/或 b 。从未调用返回的lambdas,因此没有UB。)



这是未定义的行为,与未定义的行为相同,返回一个引用堆栈分配的本地在局部超出范围之后使用该引用,除了在这种情况下它被lambda进行模糊处理。



此外,请注意,lambdas未调用 - 编译器可以在 f.first()之前自由调用 f.second()两者都是同一全表达式的一部分。因此,即使我们修复了使用引用被销毁对象引起的未定义行为, 2 0 2 1 这个程序的有效输出,你得到的取决于你的编译器决定执行lambdas的顺序。不是这是不是未定义的行为,因为编译器不能做任何事情,,而是它只是有一些自由决定

(请记住您的<$ c $

中的< c> main()函数调用自定义运算符<< 函数,函数参数的求值顺序未指定。自由地发出代码,以任何顺序评估同一全表达式中的所有函数参数,限制是函数的所有参数必须在调用该函数之前被评估。)



要解决第一个问题,请使用 std :: shared_ptr 创建引用计数的对象。通过值捕获这个共享指针,并且lambdas将保持指向的对象活着,只要它们(及其任何副本)存在。这个堆分配对象是我们将存储 a b 的共享状态的地方。



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



这是您的代码重写了未定义的行为fixed, f.first()保证在 f.second()之前被调用

  std :: pair< std :: function< int()>,std :: function< int()> addSome(){
//将a和b整数存储在包含一对的shared_ptr中。
auto numbers = std :: make_shared< std :: pair< int,int>>(0,0);

// a变成数字 - >第一个
// b变成数字 - >第二个

//我们通过值捕获shared_ptr。
return std :: make_pair([numbers] {
++ numbers-> first;
++ numbers-> second;
return numbers-> first + numbers - > second;
},[numbers] {
return numbers-> first;
});
}

int main(){
auto f = addSome();
//我们将输出分成两个语句,以保证f.first()
//在f.second()之前计算。
std :: cout<< f.first();
std :: cout<< < f.second();
return 0;
}

查看它运行。)


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

If it is not, however, changes in one lambda are not reflected in other 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. If it works then i would expect output 2 1.

解决方案

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.)

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.

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. Not 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.

(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.)

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.

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

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

(See it run.)

这篇关于Lambdas并通过引用捕获局部变量:在范围之后访问的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆