The following C++ code prints 11.1
then crashes. The lambda function seems to be called correctly inside the constructor, but then later, that same function no longer works! Why is this? Does the lambda have a limited lifespan?
#include <functional>
#include <iostream>
class LambdaStore
{
public:
LambdaStore(const std::function<void(float)>& _fn)
: fn(_fn)
{
fn(11.1f); // works
}
void ExecuteStoredLambda()
{
fn(99.9f); // crashes
}
private:
const std::function<void(float)>& fn;
};
int main()
{
LambdaStore lambdaStore([](float a) { std::cout << a << 'n'; });
lambdaStore.ExecuteStoredLambda();
}
6
4 Answers
4
Reset to default
Highest score (default)
Trending (recent votes count more)
Date modified (newest first)
Date created (oldest first)
You’re not storing a lambda, you’re storing a reference to a std::function
.
In fact, that std::function
is being created as a temporary when the lambda is implicitly converted to std::function
. That std::function
temporary dies after the line where the constructor is invoked.
LambdaStore(const std::function<void(float)>& _fn) // _fn refers to a temporary
: fn(_fn)
{
fn(11.1f); // works
} // fn (and _fn) dies
But, even if you would change your class to use the lambda type directly through a template, the lambda itself would die too, but this is true with any C++ type, no matter the type. Consider with int
s:
class LambdaStore
{
public:
LambdaStore(const int& _i)
: i(_i)
{
std::cout << i; // works
}
void ExecuteStoredLambda()
{
std::cout << i; // crashes
}
private:
const int& i;
};
void main()
{
LambdaStore lambdaStore(1); // temporary int created here
// temporary int gone
lambdaStore.ExecuteStoredLambda();
}
Temporaries don’t get lifetime extension past the statement they are created for when they are bound to a function parameter.
They do get lifetime extension if it binds directly to a member reference, when using the braces only, though:
struct ref {
const int& i
};
int main() {
ref a{3};
std::cout << a.i; // works
ref b(3);
std::cout << b.i; // crashes
}
The solution is obviously to store the std::function
by value instead of by reference:
class LambdaStore
{
public:
LambdaStore(const std::function<void(float)>& _fn)
: fn(_fn)
{
fn(11.1f); // works
}
void ExecuteStoredLambda()
{
fn(99.9f); // will also work
}
private:
std::function<void(float)> fn; // no & here
};
14
-
Good answer. I would probably suggest that
fn/_fn
"dies" at the closing parenthesis of the constructor call rather than the closing brace of the constructor itself but, since not a lot can happen between those two, it's probably irrelevant 🙂– paxdiablo2 days ago
-
2
@Peter, re your Monica-moniker, I think it's probably safe to say that Monica has "left the building", so to speak. There's going to eventually be a whole generation of SO users who don't have the faintest idea who she is 🙂
– paxdiablo2 days ago
-
2
@Peter-ReinstateMonica The lifetime of temporary is never extended when bound to a function parameter. That never changed. The temporary will live until the end of the statement, ie until
;
. When using()
to build a aggregate, the compiler synthesize a constructor function to make it more compatible to types that had constructors before C++20 so behaviour is kept. The syntax ofthing(prvalue)
never extended lifetimes. Whereastype{prvalue}
always did.– Guillaume Racicot2 days ago
-
19
@paxdiablo I would hope that such users go to my profile and educate themselves. I do not consider myself unforgiving, generally, but that incident was unforgivable. I mean, I have exactly zero connection to Monica, I find religion ludicrous, and I still ended up supporting the mod of the Jewish discussion group. I'm still angry thinking about SE's behavior. And whether Monica is around or not: SE Inc. could simply publicly say "we'd welcome you back any time if you ever wish to return". Wouldn't even take an acknowledgement of guilt. But no.
– Peter – Reinstate Monica2 days ago
-
1
@Rocketmagnet lambdas (without capture) is emplemented as an empty struct with a
operator()
defined in it. So it does use a byte on the stack. If you have captures, well, those are implemented as data member of that hidden struct, and those can take more space on the stack.std::function
on the other hand will put small and captureless lambdas on the stack, otherwise on the heap.– Guillaume Racicotyesterday
The lambda function
This might be where your understanding went astray. Lambdas are objects with member functions; they are not themselves functions. Their definitions look like function bodies, but that is really the definition of the call operator, operator()
, of the object.
A semi-corrected version of your assessment of the scenario:
The lambda object seems to call its operator correctly inside the constructor, but then later, that same object no longer works!
Why only "semi-"corrected? Because inside LambdaStore
, you do not access the lambda object directly. Instead, you access it through (a reference to) a std::function
object. A more correct version:
The
std::function
object seems to call its operator correctly inside the constructor, but then later, that same object no longer works!
Maybe this would be clearer if I take the notion of "lambda" out of the picture? Your main function is basically a syntactic shortcut for the following.
struct Functor {
void operator()(float a) {
cout << a << endl;
}
};
int main()
{
LambdaStore lambdaStore(Functor{});
lambdaStore.ExecuteStoredLambda();
}
In this version, it should be easier to see that you create a temporary object as the argument to the LambdaStore
constructor. (Actually, you create two temporaries — the explicit Functor
object and an implicit std::function<void(float)>
object.) Then you might note that you store a reference that becomes dangling as soon as the constructor finishes…
Does the lambda have a limited lifespan?
Yes, all objects with non-static (and non-thread) storage duration have limited lifespans in C++.
2
-
Thread local objects have limited lifespans, they get destroyed when the thread is destroyed.
– Daniel3 hours ago
-
Static objects, although specified by c++ to survive until the program terminates, in most operating systems can also have limited lifetimes. If a static object resides in a dynamically loaded module, it will not survive the unloading of the module. Other operating systems ignore module unloading calls, and will not free the memory or call the destructor.
– Daniel3 hours ago
Yes, a temporary variable (including a lambda) has a limited life span and a reference is not keeping it alive. You may want to store a copy instead. Your problem would be the same with any other temporary variable (like a int
) that you store a reference to. The referenced variable must outlive the reference if it’s to be valid for the lifetime of the reference.
4
-
1
And this is why I'm starting to warm to Rust, it has clearly defined and controlled ownership, and violations are caught at compile time.
– paxdiablo2 days ago
-
1
@paxdiablo C++ also has clearly defined ownership and lifetimes. You just have to know the rules..
– Jesper Juhl2 days ago
-
6
Jesper, it was more the "caught at compile time" that I was warming towards 🙂
– paxdiablo2 days ago
-
@Jesper I've been using C++ on and off since the 90s, but never as a full-time project language, but I didn't catch this one. Even compiling with
-Wall -Wextra
didn't report issue with that code despite being analyzable that a reference outlives it's value.– penguin359yesterday
In the constructor you are taking a reference to a function; and that is what is being stored. Because the function being passed into the constructor is an inline function, the reference to it is no longer valid by the time the ExecuteStoredLambda()
is called. To make it work, pass in a non-inlined function, or better, change the fn
member to be an object instance rather than a reference. ie
const std::function<void(float)> fn;
(no &)
Your Answer
Sign up or log in
Post as a guest
Required, but never shown
Post as a guest
Required, but never shown
By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.
Not the answer you're looking for? Browse other questions tagged
or ask your own question.
or ask your own question.
Reference to a temporary. Also, thanks for including a nice MCVE.
2 days ago
In general: put reference inside a class? Prepare for pain. Don't do it.
2 days ago
I'd weaken @HTNW just a slight bit: Only do so if you can guarantee the live time of the referenced object exceeding the one of the referencing object and you don't need to ever copy/move any of these two objects – though typically you won't often discover this situation…
2 days ago
Man, you people have a lot to unlearn when you encounter real lambdas.
yesterday
@Kaz C++ lambdas are real lambdas, except everything in C++ also has a lifetime that has to be manually managed, and you have very fine-grained control over capture compared to lisp-like lambdas. Remember, you can treat C++ as a pure functional language if you first remap every statement to returning a new program!
17 hours ago
|
Show 1 more comment