boost::function
[или, если ваша система поддерживает это, std::function
] позаботится о том, чтобы указатель this
удерживался достаточно хорошо, с дополнительным преимуществом - не требовать реального объекта, если он не нужен.Таким образом, вместо void (SomeType::*)(EventA)
у вас есть std::function<void(EventA)>
, и вы вызываете std::bind
в зависимости от ситуации.
subscribe(EVENT_A, std::bind(&foo::handleEventA, &foo, std::placeholders::_1));
Тривиальная функция-обертка может использоваться для обеспечения той же подписи, что вы изначально предложили, и скрытия неприятногозаполнители.
Конечно, у вас все еще есть проблема, связанная с тем, что каждый тип события имеет свою собственную подпись, и вам необходимо убедиться, что вы используете правильный код идентификатора события.В обоих случаях ваш базовый тип события может помочь.Ваш обратный вызов не должен принимать EventA&
;он может принять Event&
и dynamic_cast
до EventA
во время выполнения.Для идентификатора запросите тип напрямую.
struct Event {
virtual void ~Event() { }
virtual int ID() =0;
};
template<typename E>
struct EventHelper : Event {
virtual int ID() { return E::EventID; }
};
struct EventA : EventHelper<EventA> {
static const int EventID = 89;
};
Теперь, если у вас есть объект Event*
[когда вы отправляете свои события], вы можете сделать p->ID()
, чтобы получить соответствующий идентификатор,и если у вас есть тип EventA
[при регистрации ваших обратных вызовов], вы можете сделать EventA::EventID
.
Итак, теперь все, что вам нужно сохранить, - это std::function<void(const Event&)>
и соответствующее значение int
для каждого из ваших обратных вызовов, независимо от того, какой у вас фактический тип события.
void subscribe(int id, std::function<void(const Event&)> f) {
callbacks.insert(std::make_pair(id, f));
}
template<typename E>
void subscribe(std::function<void(const Event&)> f) {
subscribe(E::EventID, f);
}
template<typename O, typename E>
void subscribe(O* p, void (O::*f)(const Event&)) {
subscribe<E>(std::bind(f, p, std::placeholders::_1));
}
По-прежнему существует проблема, связанная с тем, что ошибка пользователя при подписке может привести к неправильному вызову функции.Если вы правильно использовали dynamic_cast
в обратном вызове, это будет обнаружено во время выполнения, но проверка во время компиляции была бы хорошей.Так что, если мы автоматизируем это dynamic_cast
?Для этого шага я собираюсь использовать лямбда-выражения c ++ 11, но она может быть реализована и в C ++ 03 с использованием различных методов.
template <class CallbackClass, class EventType>
void subscribe(CallbackClass* classInstancePtr, void (CallbackClass::*funcPtr)(EventType)) {
subscribe<EventType::EventID>([&](const Event& e) {
(classInstancePtr->*funcPtr)(dynamic_cast<const EventType&>(e));
});
}
Итак, мы завершили работу полностьюВернитесь к исходному интерфейсу, где ваши обратные вызовы принимают фактический тип, над которым они будут работать, но внутренне вы втиснули их в общую подпись.