如何处理回调注册

App*_*ell 10 c++ function-pointers callback c++11

我想实现一个回调处理程序.方法应该像以下一样简单注册......

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));
}
Run Code Online (Sandbox Code Playgroud)

......确实按预期工作.

但这种方法的问题是,取消注册回调是不可能的......

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
    }
}
Run Code Online (Sandbox Code Playgroud)

...因为无法比较绑定函数.

延迟取消注册也不起作用,因为无法验证函数对象.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

那么我应该如何处理这样的实现,如何避免遇到的问题呢?

Chr*_*ica 3

一种相当简单(但不完全干净)的方法是仅返回回调的句柄,该句柄在幕后只是映射中元素的迭代器。如果用户有一天想要注销该句柄,则他有责任存储该句柄。

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_);
}
Run Code Online (Sandbox Code Playgroud)

除了 C++14 之外,std::optional还可以仅使用 aboost::optional或 a mere std::unique_ptrwithnullptr作为无效值。

由于句柄类型的仅移动性质以及您必须显式地将句柄移动到注销函数中的事实,您在注销时会自动使其失效,并且永远不会有一个句柄引用已删除的回调(除了一个完全被摧毁的物体的事实EventManager,这需要通过两种类型的更多交织来解决)。

事实上它与Werner的解决方案类似,但更简单一些。它可以作为在其之上提供其他内容的基础,例如更高级别的基于 RAII 的自动取消注册程序等,同时仍然可以在需要/需要时访问低级别的手动取消注册。