Search code examples
c++c++11lambdashared-ptrweak-ptr

How can I track object lifetime in C++11 lambda?


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.


Solution

  • Extend object lifetime

    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.

    Track object lifetime

    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

    Track object lifetime without shared pointers

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