Reputation: 2193
Event-driven systems are used to reduce dependencies in large systems with many-to-many object interactions of various types. As such, they aim to pass arbitrary information between arbitrary objects. This is typically done by registering event handlers
with an event_manager
, using type erasure in the handler
, i.e.:
void handle_some_event(event *e) {/*Downcast e to an assumed appropriate type and work with it*/}
However, suppose we wanted to implement such a system without type erasure. It seems that certain language features, particularly generics, should make it possible. The handler
registration in the event_manager
could be templated, i.e. (loosely):
template<typename event_type>
void event_manager::register_handler(std::function<void(event_type)> handler) {
// Implementation
}
The struggle is, what should the //Implementation
of this function be? After receiving the handler, it needs to be stored in some container related to event_type
. But in order to avoid type erasure, there would have to be a container per event_type
.
One possible option is to use static containers in template classes, i.e.:
template<typename event_type>
class handler_container {
public:
inline static std::vector<std::function<void(event_type)>> handlers;
};
The event_manager
could store and execute handlers in the corresponding handler_container<event_type>::handlers
container. However, this has the obvious disadvantage that there can only really be one event_manager
, given that the containers are static and thus shared across all event_managers
. Perhaps this is sufficient for most applications, but it's still an ugly solution.
Are there any design patterns that would allow for a cleaner solution to this problem?
Upvotes: 3
Views: 918
Reputation: 1134
Not a design pattern, but in theory it looks like a potential use case for std::variant
. You can define your event type as an alias to a std::variant
of all possible event options, and your handlers would be C++20's template lambdas that check for the type they want using constexpr if
. Finally, the code that generates the event initializes the std::variant
with an appropriate event type and all handlers are invoked with std::visit
.
Use of std::variant
might seem like a potential problem of scalability, but if the handlers are written sensibly they shouldn't break when adding more variants.
I don't know of any major project that uses this "technique", so I wouldn't jump ahead to use it in production code immediately. Also, it requires C++20 to write it without (too much) boilerplate, so it might not apply for most use cases out there. But it looks doable and doesn't require type erasure.
Upvotes: 1