Alexander Guyer
Alexander Guyer

Reputation: 2193

Design patterns to construct an event-driven system in c++ without type erasure?

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

Answers (1)

Joald
Joald

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

Related Questions