Sometimes, we know nothing about lifetime of lambda that captures an object state (e.g. return it from object, register it as a callback without ability to unsubscribe etc.). How to make sure that the lambda won't access already destroyed object on invocation?
#include <iostream>
#include <memory>
#include <string>
class Foo {
public:
Foo(const std::string& i_name) : name(i_name) {}
std::function<void()> GetPrinter() {
return [this]() {
std::cout << name << std::endl;
};
}
std::string name;
};
int main() {
std::function<void()> f;
{
auto foo = std::make_shared<Foo>("OK");
f = foo->GetPrinter();
}
auto foo = std::make_shared<Foo>("WRONG");
f();
return 0;
}
This program prints "WRONG" instead of "OK" (http://ideone.com/Srp7RC) just by coincidence (it seems it just reused the same memory for the second Foo
object). Anyway, this is a wrong program. First Foo
object is already dead when we execute f
.
The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.
C/C++ use lexical scoping. The lifetime of a variable or object is the time period in which the variable/object has valid memory. Lifetime is also called "allocation method" or "storage duration."
A lambda object must not outlive any of its reference captured objects. Lambda expressions may capture objects with automatic storage duration from the set of enclosing scopes (called the reaching scope) for use in the lambda's function body.
The Lambda service stores your function code in an internal S3 bucket that's private to your account. Each AWS account is allocated 75 GB of storage in each Region. Code storage includes the total storage used by both Lambda functions and layers.
Lambdas can capture shared pointers to this
, so an object won't die while at least one lambda exists.
class Foo : public std::enable_shared_from_this<Foo> {
public:
Foo(const std::string& i_name) : name(i_name) {}
std::function<void()> GetPrinter() {
std::shared_ptr<Foo> that = shared_from_this();
return [that]() {
std::cout << that->name << std::endl;
};
}
std::string name;
};
http://ideone.com/Ucm2p8
Usually, it is not a good solution, as object lifetime is extended in very implicit manner here. It is very easy way of producing circular references between objects.
Lambdas can track captured object lifetime and use the object only if it is still alive.
class Foo : public std::enable_shared_from_this<Foo> {
public:
Foo(const std::string& i_name) : name(i_name) {}
std::function<void()> GetPrinter() {
std::weak_ptr<Foo> weak_this = shared_from_this();
return [weak_this]() {
auto that = weak_this.lock();
if (!that) {
std::cout << "The object is already dead" << std::endl;
return;
}
std::cout << that->name << std::endl;
};
}
std::string name;
};
http://ideone.com/Wi6O11
As hvd noted, we can't always be sure that an object is managed by shared_ptr
. In such case, I would suggest using the following lifetime_tracker
. It is self-contained and doesn't affect the way you manage object lifetime.
struct lifetime_tracker
{
private:
struct shared_state
{
std::uint32_t count : 31;
std::uint32_t dead : 1;
};
public:
struct monitor
{
monitor() : state(nullptr) {}
monitor(shared_state *i_state) : state(i_state) {
if (state)
++state->count;
}
monitor(const monitor& t) : state(t.state) {
if (state)
++state->count;
}
monitor& operator=(monitor t) {
std::swap(state, t.state);
return *this;
}
~monitor() {
if (state) {
--state->count;
if (state->count == 0 && state->dead)
delete state;
}
}
bool alive() const {
return state && !state->dead;
}
private:
shared_state *state;
};
public:
lifetime_tracker() : state(new shared_state()) {}
lifetime_tracker(const lifetime_tracker&) : state(new shared_state()) {}
lifetime_tracker& operator=(const lifetime_tracker& t) { return *this; }
~lifetime_tracker() {
if (state->count == 0)
delete state;
else
state->dead = 1;
}
monitor get_monitor() const {
return monitor(state);
}
private:
shared_state *state;
};
Example of usage
class Foo {
public:
Foo(const std::string& i_name) : name(i_name) {}
std::function<void()> GetPrinter() {
auto monitor = tracker.get_monitor();
return [this, monitor]() {
if (!monitor.alive()) {
std::cout << "The object is already dead" << std::endl;
return;
}
std::cout << this->name << std::endl;
};
}
private:
lifetime_tracker tracker;
std::string name;
};
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