Как обрабатывать регистрацию обратного вызова - программирование
Подтвердить что ты не робот

Как обрабатывать регистрацию обратного вызова

Я хочу реализовать обработчик обратного вызова. Методы должны регистрироваться так легко, как следующие...

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

... потому что невозможно сравнивать связанные функции.

Lazy deregistering также не будет работать, потому что объект функции не может быть проверен.

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

Итак, как мне подойти к такой реализации, как можно избежать проблем, с которыми я столкнулся?

4b9b3361

Ответ 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_);
}

Вместо С++ 14 std::optional можно было просто использовать boost::optional или просто std::unique_ptr с nullptr как недопустимое значение.

Из-за характера движения, характерного только для перемещения, и того факта, что вы должны явно перемещать дескриптор в функцию отмены регистрации, вы автоматически получаете его недействительным при делегировании регистрации и никогда не сможете иметь дескриптор, ссылающийся на уже удаленную обратную связь ( За исключением факта полностью разрушенного объекта EventManager, который нужно будет решить путем более переплетения двух типов).

На самом деле это похоже на решение Вернера, но немного проще. Это может быть базой для предоставления дополнительных материалов поверх него, например, на более высоких уровнях автоматических RAI-основанных дерегистеров и т.д., Все еще имея доступ к ручному дерегистрации низкого уровня при необходимости/желании.

Ответ 2

Но проблема с этим подходом заключается в том, что невозможно отменить регистрацию обратного вызова...

У меня была следующая проблема в прошлом. Я решил это, используя список, чтобы гарантировать, что итераторы не были аннулированы, и я вернул объект Unmapper, который был связан с итератором в списке и не был отображен, когда он вышел из сферы видимости. Unmap удалит итератор из списка функций:

Непоглотитель имел следующий аромат:

//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, так как клиенту нужно только "размонтировать", когда "отображаемый" выходит за рамки.

Я кратко опускал нерелевантный код. Можно также отметить, что у меня было много вызовов, сопоставленных с состояниями, и на более высоких уровнях состояния жили на карте. Это не имеет значения, так же как и в списках, итераторы карт не являются недействительными и поэтому могут использоваться во время отмены регистрации.

Ответ 3

Простым решением является обертка объекта обратного вызова в объекте, который имеет член id, а затем возвращает id из регистрационного вызова, чтобы это значение можно было использовать для отмены регистрации обратного вызова.

Другой вариант - использовать

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

для реестра.