11

I want to implement a callback handler. Methods should be registered as easy as the following...

std::multimap<Event::Type, std::function<void()>> actions;

void EventManager::registerAction(Event::Type event, std::function<void()> action) {
    actions.insert(std::make_pair(event, action));
}

...which indeed works as intended.

But the problem with that approach is, that it is impossible to deregister a callback...

void EventManager::deregisterAction(Event::Type event, std::function<void()> action) {
    for(auto i = actions.lower_bound(event); i != actions.upper_bound(event); ++i) {
        // if action == i->second
    }
}

...because it's impossible to compare bound functions.

Lazy deregistering also won't work because the function object can't be validated.

void EventManager::handle(Event::Type event) {
    for(auto i = actions.lower_bound(event); i != actions.upper_bound(event); ++i) {
        if(i->second) // returns true even if the object doesn't exist anymore
            i->second();
    }
}

So how should I approach an implementation like this, how can the issues I encountered be avoided?

4

3 に答える 3

2

かなり単純な (まだ完全にきれいではない) 方法の 1 つは、ハンドルをコールバックに返すことです。これは、内部的にはマップ内の要素へのイテレータにすぎません。ユーザーは、いつか登録を解除したい場合に、このハンドルを格納する責任があります。

class CallbackHandle
{
    friend class EventManager;

public:
    CallbackHandle() = default;
    CallbackHandle(const CallbackHandle&) = delete;
    CallbackHandle& operator=(const CallbackHandle&) = delete;

    bool isValid() const { return iter_; }
    Event::Type event() const { return iter_.value()->first; }
    void invoke() const { iter_.value()->second(); }

private:
    typedef std::multimap<Event::Type, std::function<void()>>::iterator Iterator;

    CallbackHandle(Iterator iter) : iter_(iter) {}

    std::optional<Iterator> iter_;
};

CallbackHandle EventManager::registerAction(Event::Type event, 
                                            std::function<void()> action)
{
    return CallbackHandle(actions.insert(std::make_pair(event, action)));
}

void EventManager::deregisterAction(CallbackHandle handle)
{
    actions.erase(handle.iter_);
}

C++14 の代わりに、単に a または aを無効な値としてstd::optional使用することもできます。boost::optionalstd::unique_ptrnullptr

ハンドル型の移動のみの性質と、ハンドルを登録解除関数に明示的に移動する必要があるという事実により、登録解除時にハンドルが自動的に無効になり、既に削除されたコールバックを参照するハンドルを持つことはできません (完全に破壊されたEventManagerオブジェクトの事実であり、2 つのタイプをもう少し絡み合わせて解決する必要があります)。

実際、これはWernerのソリューションに似ていますが、少し単純です。これは、高レベルの自動 RAII ベースの登録解除機能などの追加機能を提供するためのベースになる可能性がありますが、必要/必要なときに低レベルの手動登録解除にアクセスすることもできます。

于 2013-09-16T08:57:01.293 に答える
1

簡単な解決策は、メンバーを持つオブジェクトでコールバック オブジェクトをラップし、登録呼び出しからidを返して、値を使用してコールバックの登録を解除できるようにすることです。id

別のオプションは、使用することです

std::map<std::pair<EventType, int>, std::function<void()>>

レジストリ用。

于 2013-09-16T08:57:10.377 に答える