Как я могу отслеживать время жизни объекта в C++11 лямбда?

Иногда нам ничего не известно о времени жизни лямбды, которая фиксирует состояние объекта (например, возвращает его из объекта, регистрирует как обратный вызов без возможности отписаться и т. Д.). Как убедиться, что лямбда не получит доступ к уже уничтоженному объекту при вызове?

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

Эта программа печатает "НЕПРАВИЛЬНО" вместо "ОК" ( http://ideone.com/Srp7RC) просто по стечению обстоятельств (кажется, она просто повторно использовала ту же память для второй Foo объект). Во всяком случае, это неправильная программа. Первый Foo объект уже мертв, когда мы выполняем f,

2 ответа

Решение

Продлить срок службы объекта

Lambdas может захватывать общие указатели на thisпоэтому объект не умрет, пока существует хотя бы одна лямбда.

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

Обычно это не очень хорошее решение, так как время жизни объекта здесь неявно увеличивается. Это очень простой способ создания круговых ссылок между объектами.

Отслеживание времени жизни объекта

Лямбды могут отслеживать время жизни захваченного объекта и использовать объект, только если он еще жив.

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

Отслеживание времени жизни объекта без общих указателей

Как отметил hvd, мы не всегда можем быть уверены, что объектом управляет shared_ptr, В таком случае я бы предложил использовать следующее lifetime_tracker, Он самодостаточен и не влияет на способ управления временем жизни объекта.

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

Пример использования

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

Ответ Стаса хорош, когда вы можете быть уверены, что объекты управляются shared_ptr, но это не всегда возможно. Однако вы всегда можете отслеживать время жизни объектов и добавлять утверждение в свою лямбду.

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

В конструкторе общий указатель monitor явно не инициализируется, но настраивается через инициализатор так, чтобы он указывал на thisи ничего не делать после истечения срока жизни указателя. Идея не в том, чтобы сделать shared_ptr ответственность за освобождение объекта, идея состоит только в том, чтобы shared_ptr передать информацию о времени жизни объекта.

До создания вашей лямбды, вы можете построить weak_ptr который отслеживает связанный shared_ptr, Если объект был уничтожен, то его monitor член обязательно будет также уничтожен, и это становится видимым через expired() функция.

Другие вопросы по тегам