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

...确实按预期工作。

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

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

...因为不可能比较绑定函数

延迟注销也不起作用,因为无法验证函数对象。

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();
    }
}

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

4

3 回答 3

2

一种相当简单(但不完全干净)的方法是只返回一个回调的句柄,它在引擎盖下只是映射中元素的迭代器。如果用户想在某天取消注册,则用户负责存储此句柄。

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 的std::optional,也可以只使用 a或boost::optionala作为无效值。std::unique_ptrnullptr

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

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

于 2013-09-16T08:57:01.293 回答
1

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

我过去遇到过以下问题。我通过使用列表来解决它以确保迭代器不会失效,并且我返回了一个 Unmapper 对象,该对象与列表中的迭代器相关联,并且在超出范围时取消映射。Unmap 将从函数列表中删除迭代器:

unmapper 具有以下特点:

//Polymorphic destruction being the only purpose....
struct Resource{ virtual ~Resource(){} };

template <class SubjectT, class ListT>
class Unmapper : public Resource
{
  public:
    Unmapper( std::shared_ptr<SubjectT> state,
      typename ListT::iterator ref ) :
      subject_( state ),
      ref_( ref )
    {
    }
    ~Unmapper()
    {
      std::shared_ptr<SubjectT> subject = subject_.lock();
      if( subject )
      {
        subject->unmap( ref_ );
      }
    }

  private:
    std::weak_ptr<SubjectT> subject_;
    typename ListT::iterator ref_;
};

和...

    typedef std::function<void()> StateNotifier;

    class StateImpl :
      public std::enable_shared_from_this<StateImpl>
      //And some others...
    {
      typedef std::list<StateNotifier> MappedCallList;

      //....
      virtual std::unique_ptr<Resource> mapToCall( const StateNotifier& callback )
      {
        MappedCallList::iterator callRef =
          mappedCalls_.insert( mappedCalls_.end(), callback );
        return std::unique_ptr<Resource>( new Unmapper<
          StateImpl,MappedCallList>( shared_from_this(), callRef ) );
      }

      //No brainer...
      void unmap( MappedCallList::iterator i ){ mappedCalls_.erase( i ); }
      //...
    };

现在用户需要保留 mapToCall 的返回值,直到他不再需要它,然后 RAII 会自动取消映射。

人们可以很容易地修改它以使用地图。Unmapper 通过 Resource 接口对客户端隐藏,因为客户端只需要在“映射”超出范围时“取消映射”。

为简洁起见,我省略了不相关的代码。还可以注意到,我有许多调用映射到状态,并且在更高级别上,状态存在于映射中。这无关紧要,与列表一样,映射迭代器不会失效,因此可以在注销期间使用。

于 2013-09-16T07:15:18.893 回答
1

一个简单的解决方案是将回调对象包装在具有id成员的对象中,然后id从注册调用中返回 ,以便可以使用该值取消注册回调。

另一种选择是使用

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

为注册表。

于 2013-09-16T08:57:10.377 回答