Home > Software design >  C choose method based argument's dynamic type
C choose method based argument's dynamic type

Time:11-07

Question

Imagine I have some simulator library, that takes from me some objects (aka event handlers) and generates events for these objects by calling their handle_event(Event) method. The library provides me with the following classes:

class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class


// Object aka event handler. I should inherit this class and then give objects to the simulator
class AbstractEventHandler
{
public:
        virtual void handle_event(Event) = 0;
};

I want to implement an object that handles different events differently. The first code I came up with is the following:

#include <iostream>

class MyObject : public AbstractEventHandler
{
public:
        void actual_handle_event(SomeParticularEvent)
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void actual_handle_event(AnotherParticularEvent)
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void actual_handle_event(Event e)
        {
                std::cerr << "Unknown event type occurred\n";
        }

        virtual void handle_event(Event e) override
        {   
                actual_handle_event(e);
        }   
};

Сontrary to my expectations, MyObject::handle_event(Event) will always call MyObject::actual_handle_event(Event) regardless of the dynamic type of e.

My question is: What is the correct way to implement MyObject (preferrably, making it possible to easily add new event type)?


All the code together

#include <iostream>


class Event {}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {}; // Some event class
class AnotherParticularEvent : public Event {}; // Some event class


class AbstractEventHandler
{
public:
        virtual void handle_event(Event) = 0;
};


class MyObject : public AbstractEventHandler
{
public:
        void actual_handle_event(SomeParticularEvent)
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void actual_handle_event(AnotherParticularEvent)
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void actual_handle_event(Event e)
        {
                std::cerr << "Unknown event type occurred\n";
        }

        virtual void handle_event(Event e) override
        {
                actual_handle_event(e);
        }
};


int main()
{
        MyObject o{};
        o.handle_event(SomeParticularEvent{}); // Prints "Unknown event type occurred"
}

Additional question

Is it also possible to create a class derived from MyObject and implement additional event handlers (not override old ones, but add support for new events) without rewriting the handle_event method in the derived class?

CodePudding user response:

Based on the requirements in the question and things discussed in the comments to @ypnos answer, I believe that you want or need to implement the visitor pattern here.

One possible implementation would look like this (based on the Wikipedia article about Visitor Pattern: https://en.wikipedia.org/wiki/Visitor_pattern#C _example)

#include <iostream>

// Forward declare all Event classes
class Event;
class SomeParticularEvent;
class AnotherParticularEvent;

class AbstractEventHandler
{
public:
        virtual void handle_event(const Event&) = 0;
        virtual void handle_event(const SomeParticularEvent&) = 0;
        virtual void handle_event(const AnotherParticularEvent&) = 0;
};

class Event {
public:
    // virtual function for accepting the "visitor"
    virtual void accept(AbstractEventHandler& handler) {
        handler.handle_event(*this);
    }
}; // Base class for all events
// All event classes are derived from `Event`
class SomeParticularEvent : public Event {
public:
    // Needs to be overriden to call `handle_event` with the correct type
    void accept(AbstractEventHandler& handler) override {
        handler.handle_event(*this);
    }
}; // Some event class
class AnotherParticularEvent : public Event {
public:
    void accept(AbstractEventHandler& handler) override {
        handler.handle_event(*this);
    }
}; // Some event class

class MyObject : public AbstractEventHandler
{
public:
        void handle_event(const SomeParticularEvent&) override
        {
                std::cout << "`SomeParticularEvent` occurred\n";
        }

        void handle_event(const AnotherParticularEvent&) override
        {
                std::cout << "`AnotherParticularEvent` occurred\n";
        }

        void handle_event(const Event& e) override
        {
                std::cerr << "Unknown event type occurred\n";
        }
};


int main()
{
        MyObject o{};
        SomeParticularEvent{}.accept(o);
        AnotherParticularEvent{}.accept(o);
}

CodePudding user response:

In your method handle_event() object slicing occurs. You should pass (const) references for the inheritance to work correctly, or pointers.

However, this does not help with your case when dispatching internally from handle_event() to actual_handle_event().

A brute-force solution would be to explicitely dispatch by trying dynamic_casts. You would need to override handle_event() in derived classes to extend it. It also doesn't look that pretty (indicating a design problem).

What is typically done in these situations is that the objects in question (Event) would provide an interface for querying the type, e.g. an abstract type() getter that is overridden.

  • Related