Search code examples
c++eventsevent-handlinggame-engineunions

Event system: Inheritance with type-casting or unions


I am currently working on an event system for my game engine. I've thought about two ways I could implement it:

1. With inheritance:

class Event
{
    //...
    Type type; // Event type stored using an enum class
}

class KeyEvent : public Event
{
    int keyCode = 0;
    //...
}

2. With unions:

class Event
{
    struct KeyEvent
    {
        int keyCode = 0;
        //...
    }

    Type type; // Event type stored using an enum class

    union {
        KeyEvent keyEvent;
        MouseButtonEvent mouseButtonEvent;
        //...
    }
}

In both cases you have to check the Event::Type enum, to know which kind of event has occured.

When you use inheritance, you have to cast the event to get it's values (e.g. cast Event to KeyPressedEvent to get key code). So you have to cast raw pointers to the neeeded type, which is often very error prone.

When you use the union you can simply retrieve the event that has occured. But this does also mean that the size of the union in memory is always as big as it's largest possible class contained in it. This means that a lot of memory may be wasted.

Now to the question:

Are there any arguments to use one way over the other? Should I waste memory or take the risk of wrong casting?

Or is there a better solution to this which I haven't thought of?

In the end I just want to pass e.g. a KeyEvent as an Event. Then I want to check the Event::Type using the enum class. If the type is equal to say Event::Type::Key I want to get the data of the key event.


Solution

  • Event seems more data-centric than behavior-centric, so I would choose std::variant:

    using Event = std::variant<KeyEvent, MouseButtonEvent/*, ...*/>;
    // or using Event = std::variant<std::unique_ptr<KeyEvent>, std::unique_ptr<MouseButtonEvent>/*, ...*/>;
    // In you are concerned to the size... at the price of extra indirection+allocation
    

    then usage might be similar to

    void Print(const KeyEvent& ev)
    {
        std::cout << "KeyEvent: " << ev.key << std::endl;
    }
    void Print(const MouseButtonEvent& ev)
    {
        std::cout << "MouseButtonEvent: " << ev.button << std::endl;
    }
    
    // ...
    
    void Print_Dispatch(const Event& ev)
    {
        std::visit([](const auto& ev) { return Print(ev); }, ev);
    }