【问题标题】:How can I track object lifetime in C++11 lambda?如何在 C++11 lambda 中跟踪对象生命周期?
【发布时间】:2015-02-27 12:56:13
【问题描述】:

有时,我们对捕获对象状态的 lambda 的生命周期一无所知(例如,从对象返回它,将其注册为回调而无法取消订阅等)。如何确保 lambda 在调用时不会访问已销毁的对象?

#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对象重用了相同的内存)。无论如何,这是一个错误的程序。当我们执行f 时,第一个Foo 对象已经死了。

【问题讨论】:

  • 代码不被clang接受
  • @P0W 嗯,也许 clang 已经过时了?我猜clang-3.3应该编译这个(现在不能检查)。
  • 是的,最新的compile,谢谢!

标签: c++ c++11 lambda shared-ptr weak-ptr


【解决方案1】:

延长对象生命周期

Lambda 可以捕获指向this 的共享指针,因此当至少存在一个 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

通常,这不是一个好的解决方案,因为这里以非常隐含的方式延长了对象的生命周期。这是在对象之间产生循环引用的非常简单的方法。

跟踪对象生命周期

Lambdas 可以跟踪捕获的对象生命周期并仅在对象还活着时才使用它。

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 noted,我们不能总是确定一个对象是由 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;
};

【讨论】:

    【解决方案2】:

    当您可以确定对象由shared_ptr 管理时,Stas 的回答很好,但这并不总是可能的。不过,您始终可以做的是跟踪对象的生命周期,并在您的 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};
    };
    

    在构造函数中,共享指针monitor 没有显式初始化,而是通过它的初始化器设置为指向this,并且一旦指针的生命周期到期就什么也不做。这个想法不是让shared_ptr 负责释放对象,而是让shared_ptr 传递有关对象生命周期的信息。

    在创建 lambda 之前,您可以构造一个 weak_ptr 来跟踪关联的 shared_ptr。如果对象已被销毁,则其monitor 成员也必然已被销毁,并且通过expired() 函数可见。

    【讨论】:

    • 是的,好主意。但是您需要定义复制构造函数。现在,如果您复制 Foo 对象,它将表现不正确。
    • @Stas 对,感谢您指出这一点。还有一个移动构造函数和赋值运算符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-06
    • 2012-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多