How can I track the lifetime of an object in c ++ 11 lambda?

Sometimes we donโ€™t know anything about the lambda lifetime, which captures the state of an object (for example, returns it from an object, registers it as a callback without the ability to unsubscribe, etc.). How to make sure that a lambda will not get access to an already destroyed object when called?

#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 that it just reused the same memory for the second Foo object). In any case, this is the wrong program. The first Foo object is already dead when we do f .

+5
source share
2 answers

Extend facility life

Lambdas can grab common pointers before this , so the object will not die, although there is at least one lambda.

 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 this is not a good solution, since the lifetime of the object here is very implicit. This is a very simple way to create circular links between objects.

Tracking Object Lifetime

Lambdas can track the lifetime of a captured object 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

Life expectancy of an object without common pointers

As stated in hvd, we cannot always be sure that the object is controlled by shared_ptr . In that case, I would suggest using the following lifetime_tracker . It is autonomous and does not affect the way of controlling the lifetime of an object.

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

Usage example

 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; }; 
+12
source

Stas's answer is good when you can be sure that objects are managed using shared_ptr , but this is not always possible. However, you can always track the lifetime of objects and add a statement to your lambda.

 void ignore(void *) { } class Foo { public: Foo(const std::string& i_name) : name(i_name) {} Foo(const Foo& other) : name(other.name) {} Foo(Foo&& other) : name(std::move(other.name)) {} Foo& operator=(Foo other) { swap(*this, other); return *this; } friend void swap(Foo& a, Foo& b) { using std::swap; swap(a.name, b.name); } std::function<void()> GetPrinter() { std::weak_ptr<void> monitor = this->monitor; return [=]() { assert (!monitor.expired()); std::cout << name << std::endl; }; } std::string name; private: std::shared_ptr<void> monitor{this, ignore}; }; 

In the constructor, the general monitor pointer is not explicitly initialized, but is configured through its initializer to point to this , and do nothing after the expiration of the pointer. The idea is not to make shared_ptr responsible for freeing an object, the idea is only to allow shared_ptr pass information about the lifetime of the object.

Before creating your lambda, you can build a weak_ptr that tracks the associated shared_ptr . If the object was destroyed, its member monitor will also be destroyed and will be visible using the expired() function.

+5
source

Source: https://habr.com/ru/post/1210175/


All Articles