6

オブジェクトの状態をキャプチャするラムダの有効期間について何も知らない場合があります (たとえば、オブジェクトから返す、サブスクライブ解除できないコールバックとして登録するなど)。呼び出し時にラムダが既に破棄されたオブジェクトにアクセスしないようにする方法は?

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

このプログラムは、偶然にも「OK」( http://ideone.com/Srp7RC )の代わりに「WRONG」を出力します(2番目のFooオブジェクトに同じメモリを再利用したようです)。とにかく、これは間違ったプログラムです。を実行すると、最初Fooのオブジェクトはすでに死んでいますf

4

2 に答える 2

14

オブジェクトの有効期間を延長する

ラムダは への共有ポインタをキャプチャできるthisため、少なくとも 1 つのラムダが存在する間、オブジェクトは停止しません。

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_ptrlifetime_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;
};
于 2014-12-31T12:18:53.373 に答える