简体   繁体   中英

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

Sometimes, we know nothing about lifetime of lambda that captures an object state (eg 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 .

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

Stas's answer is good when you can be certain that objects are managed by a shared_ptr , but that isn't always possible. What you can always do, though, is track the lifetime of objects, and add an assertion in 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 shared pointer monitor is not explicitly initialised, but set up through its initialiser to point to this , and to do nothing once the pointer's lifetime expires. The idea isn't to make shared_ptr responsible for freeing the object, the idea is only to let shared_ptr pass along information on the object's lifetime.

Prior to the creation of your lambda, you can construct a weak_ptr which tracks the associated shared_ptr . If the object has been destroyed, then its monitor member will necessarily also have been destroyed, and that becomes visible through the expired() function.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM