Как упоминалось в другом ответе, это область с большим количеством ранее существовавшего искусства. Рекомендованные поисковые термины для дополнительных исследований: «двойная отправка», «множественная отправка» и «открытые мульти-методы».
При решении таких проблем я считаю, что лучше всего подумать о том, откуда у человека есть информация и кактранспортируйте это туда, где это должно быть. При добавлении обработчика у вас есть тип события, и при его использовании вам нужно воссоздать событие как правильный тип. Таким образом, предложение другого ответа о добавлении дополнительной логики вокруг обработчика является достойным подходом. Однако вы не хотите, чтобы ваши пользователи делали это. Если вы отправляете информацию о типе события вниз на один уровень, вы можете сделать это самостоятельно для своего пользователя, например:
template<typename EventParameter, typename Function>
void add_event_handler(Function&& f)
{
event_handlers[event_type<EventParameter>()] = [=](std::unique_ptr<Event> e) {
f(event_cast<EventParameter>(std::move(e)));
};
}
Пользователи могут использовать это легко, с производным типом, без дополнительной упаковки требуетсяв конце:
add_event_handler<SomeEvent>([](std::unique_ptr<SomeEvent>) {
e->bar();
});
Это примерно так же просто или сложно, как и раньше, но вместо перечислителя передается аргумент шаблона, чтобы сообщить тип события.
Aочень связанное беспокойство избавляется от склонного к ошибкам шаблона get_event_type
. Если кто-то указывает неправильное значение, случаются плохие вещи. Если вы можете использовать RTTI, вы можете позволить компилятору выбрать разумный ключ карты:
// Key for the static type without an event
template<typename EventParameter>
auto event_type() -> std::type_index
{
return std::type_index(typeid(EventParameter));
}
// Key for the dynamic type given an event
template<typename EventParameter>
auto event_type(const EventParameter& event) -> std::type_index
{
return std::type_index(typeid(event));
}
std::map<std::type_index, EventHandler> event_handlers;
Это связано, поскольку вы можете наложить ограничение на то, что ключ карты и аргумент обработчика совпадают, тем дешевле вы можете преобразовать событие обратно в исходную форму. Например, в приведенном выше примере static_cast может работать:
template<typename To, typename From>
std::unique_ptr<To> event_cast(std::unique_ptr<From> ptr)
{
return static_cast<To*>(ptr.release());
}
. Конечно, в этом подходе и использовании RTTI, свернутого вручную, есть очень тонкая разница. Это отправляется на основе динамического типа, в то время как с помощью RTTI, полученного вручную, вы можете получить дополнительную информацию, добавив к событию, чтобы обработчик не знал об этом. Но несоответствие между свернутым вручную типом RTTI и фактическим динамическим типом также может быть ошибкой, поэтому оно сокращает оба пути.