Lambdas and Capture by Reference Local Variables:Accessing After the Scope

Lambdas and capture by reference local variables : Accessing after the scope

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

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

What happens if I capture a local variable by reference, and it goes out of scope?

Yes, that would be following a dangling reference. It sounds like you're worried about interface design: "I am almost positive that someone would end up doing it." Please don't reject lambdas and std::function on this basis, as they are no more dangerous than any other alternative. Lambdas are just a simpler way to define local functors. std::function is the best interface to persistent, polymorphic functors, lambda or not.

The scope issue is why it's easier to capture by value. The user won't get a reference unless they write &. Of course, the danger is that someone would get in the habit of starting all their lambda functions with [&], since references are "faster." Hopefully any such person would learn their lesson soon enough… although some pointer-happy folks are just incorrigible.

What happens if you return a reference to a local variable through a lambda function object?

In your case, you are invoking undefined behaviour. The i name is local to get_lambda() function, and once i gets out of scope, it gets destroyed. So, with your lambda, you are now storing a reference to something that isn't there anymore. This is also known as a dangling reference. Capture the local variable by value instead:

auto lambda = [i]() { std::cout << i << '\n'; };

or:

auto lambda = [=]() { std::cout << i << '\n'; };

You are indeed allowed to capture locals by reference in the lambda's capture-list. Hence, no compiler error. Depending on the compiler, a warning might be issued.

Using lambdas in local scope in c++

Lambdas with empty brackets are equivalent with function pointers and will be copied by value in the same way. Answer yes.

using out of scope variables in C++11 lambda expressions

You need to capture the variable, either by value (using the [=] syntax)

bool repeated = std::any_of(agents.begin(), agents.end(),
[=](P_EndPoint i)->bool
{return requestPacket.identity().id()==i.id();});

or by reference (using the [&] syntax)

bool repeated = std::any_of(agents.begin(), agents.end(),
[&](P_EndPoint i)->bool
{return requestPacket.identity().id()==i.id();});

Note that as @aschepler points out, global variables with static storage duration are not captured, only function-level variables:

#include <iostream>

auto const global = 0;

int main()
{
auto const local = 0;

auto lam1 = [](){ return global; }; // global is always seen
auto lam2 = [&](){ return local; }; // need to capture local

std::cout << lam1() << "\n";
std::cout << lam2() << "\n";
}

Returning a lambda capturing a local variable

int x has a limited lifetime. References to automatic storage variables (what you call "the stack") are only valid over the variable's lifetime. In this case, only until the end of the stack frame (the scope) where the variable exists, or the function for function arguments.

[&] captures any mentioned ("local") variable by reference, except this (which is captured by value if used or implicitly used). [=] captures any mentioned variable by value. [x] would capture x explicitly, and [&x] by reference explicitly. In C++17, [*this] also works.

There is also [x=std::move(x)], or [blah=expression].

In general, if the lambda will outlive the current scope don't use [&]: be explicit about what you capture.



Related Topics



Leave a reply



Submit