Как я могу отслеживать время жизни объекта в 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;
};
Обычно это не очень хорошее решение, так как время жизни объекта здесь неявно увеличивается. Это очень простой способ создания круговых ссылок между объектами.
Отслеживание времени жизни объекта
Лямбды могут отслеживать время жизни захваченного объекта и использовать объект, только если он еще жив.
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;
};
Отслеживание времени жизни объекта без общих указателей
Как отметил 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()
функция.