In the code below, I create a lambda that captures a local variable by reference. Note that it is a pointer, so, if C++ lambdas are true closures, it should survive the lifetime of the function that creates the lambda.
However, when I call it again, rather than creating a new local variable (a new environment) it reuses the same as before, and in fact, captures exactly the same pointer as before.
This seems wrong. Either, C++ lambdas are not true closures, or is my code incorrect?
Thank you for any help
#include <iostream>
#include <functional>
#include <memory>
std::function<int()> create_counter()
{
std::shared_ptr<int> counter = std::make_shared<int>(0);
auto f = [&] () -> int { return ++(*counter); };
return f;
}
int main()
{
auto counter1 = create_counter();
auto counter2 = create_counter();
std::cout << counter1() << std::endl;
std::cout << counter1() << std::endl;
std::cout << counter2() << std::endl;
std::cout << counter2() << std::endl;
std::cout << counter1() << std::endl;
return 0;
}
This code returns:
1
2
3
4
5
But I was expecting it to return:
1
2
1
2
3
Further edit:
Thank you for pointing the error in my original code. I see now that what is happening is that the pointer gets deleted after the invocation of create_couter, and the new create simply reuses the same memory address.
Which brings me to my real question then, what I want to do is this:
std::function<int()> create_counter()
{
int counter = 0;
auto f = [&] () -> int { return ++counter; };
return f;
}
If C++ lambdas were true closures, each local counter will coexist with the returned function (the function carries its environment--at least part of it). Instead, counter is destroyed after the invocation of create_counter, and calling the returned function creates a segmentation fault. That is not the expected behaviour of a closure.
Marco A has suggested a work around: make the pointer passed by copy. That increases the reference counter, so it does not get destroyed after create_counter. But that is kludge. But, as Marco pointed out, it works and does exactly what I was expecting.
Jarod42 proposes to declare the variable, and initialize it as part of the capture list. But that defeats the purpose of the closure, as the variables are then local to the function, not to the environment where the function is created.
apple apple proposes using a static counter. But that is a workaround to avoid the destruction of the variable at the end of create_function, and it means that all returned functions share the same variable, not the environment under which they run.
So i guess the conclusion (unless somebody can shed more light) is that lambdas in C++ are not true closures.
thank you again for your comments.
Lambda functions may be implemented as closures, but they are not closures themselves. This really depends on the context in which you use your application and the environment. When you are creating a lambda function that uses non-local variables, it must be implemented as a closure.
All lambdas are inline. Not all calls to them are necessarily inlined.
By default, variables are captured by const value . This means when the lambda is created, the lambda captures a constant copy of the outer scope variable, which means that the lambda is not allowed to modify them.
In C++, lambda expression constructs a closure, an unnamed function object capable of capturing variables in scope. It still sounds ambiguous, at least to me. Closure is a general concept in programming that originated from functional programming.
The shared pointer is being destroyed at the end of the function scope and the memory is being freed: you're storing a dangling reference
std::function<int()> create_counter()
{
std::shared_ptr<int> counter = std::make_shared<int>(0);
auto f = [&]() -> int { return ++(*counter); };
return f;
} // counter gets destroyed
Therefore invoking undefined behavior. Test it for yourself by substituting the integer with a class or struct and check if the destructor actually gets called.
Capturing by value would have incremented the usage counter of the shared pointer and prevented the problem
auto f = [=]() -> int { return ++(*counter); };
^
As mentioned, you have dangling reference as the local variable is destroyed at end of the scope.
You can simplify your function to
std::function<int()> create_counter()
{
int counter = 0;
return [=] () mutable -> int { return ++counter; };
}
or even (in C++14)
auto create_counter()
{
return [counter = 0] () mutable -> int { return ++counter; };
}
Demo
Lambda expression — A lambda expression specifies an object specified inline, not just a function without a name, capable of capturing variables in scope.
Closures -
Closures are special functions that can capture the environment, i.e. variables within a lexical scope*.*
A closure is any function that closes over the environment in which it was defined. This means that it can access variables, not in its parameter list.
In c++ a lambda expression is the syntax used to create a special temporary object that behaves similarly to how function objects behave.
The C++ standard specifically refers to this type of object as a closure object. This is a little bit at odds with the broader definition of a closure, which refers to any function, anonymous or not, that captures variables from the environment they are defined in.
As far as the standard is concerned, all instantiations of lambda expressions are closure objects, even if they don’t have any captures in their capture group.
https://pranayaggarwal25.medium.com/lambdas-closures-c-d5f16211de9a
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With