3

I am creating a simple event system where multiple listeners can be notified on a specific topic and when an event is fired, it can pass a generic payload to the event, and the listeners will match the format of the fired event. However, because it's not possible to use templates on a virtual function, how else can I achieve this?

class AEventListener
{
public:

    template<class T>
    struct PayloadObject {
        T obj;
    };

    explicit AEventListener();
    virtual ~AEventListener();

//error here because T is undefined. Each PayloadObject may have a different type
    virtual void notify(vector<shared_ptr<PayloadObject<T>>> payload) = 0;
};

The notify method is called when an event topic has a listener subscribed, but I want a generic way of just passing a load of random objects to the listener.

For example

fireEvent("test.topic", Payload { 0, "hello", 123 });
//...
listener.notify(payload);

How would I go about this in C++?


I have managed to get around this, although I don't think this is the best way and could slow down performance.

template<class T>
struct PayloadObject : public APayloadObject {
    T obj;

    PayloadObject(T obj) {
        this->obj = obj;
    }

    ~PayloadObject() override {

    };

};

struct APayloadObject {
    virtual ~APayloadObject();
};

Firing:

vector<shared_ptr<APayloadObject>> payload;
payload.push_back(shared_ptr<PayloadObject<int>>(new PayloadObject<int>(5))); //payload[0] = int - 5
Events::fire(EventKeys::DISCONNECTION_EVENT, payload);

Notifying:

shared_ptr<PayloadObject<int>> number = dynamic_pointer_cast<PayloadObject<int>>(payload[0]);
int id = number.get()->obj; //payload[0] = int - 5
jjmcc
  • 795
  • 2
  • 11
  • 27
  • 2
    The point of virtual functions is dynamic dispatch. The point of templates is static dispatch. You can't have a statically determined type inside of a dynamically dispatched function. It doesn't work that way. – Silvio Mayolo Feb 03 '18 at 22:09
  • One notice - use `make_shared` instead of using new inside `shared_ptr` constructor – bartop Feb 04 '18 at 00:36
  • @bartop thanks for the feedback - is there a reason for this? – jjmcc Feb 04 '18 at 14:59
  • @jjmcc Most importantly - `make_shared` makes one heap allocation while `new` version does two - first for object and second for control block. Using `new` in context like this can also lead to unexpected memory leak. This post: https://stackoverflow.com/questions/18301511/stdshared-ptr-initialization-make-sharedfoo-vs-shared-ptrtnew-foo gives pretty clear answer – bartop Feb 04 '18 at 15:05

1 Answers1

2

One simple approach is to come up with a common base or common interface for the Payload objects. So that they are not a template class.

struct Payload {
  virtual ~Payload() = default;
  virtual std::string foo() const;
  virtual std::string bar() const;
};

Another way is to use a variant type for the payload objects:

using Message_t = boost::variant<A, B, C>;

and then make AEventListener take the Message_t type so that it doesn't require the member function to be a template.

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notify(std::vector<Message_t> payload) = 0;
};

In C++17 you could use std::variant for this instead of boost.

Yet another way is to skip using a variant, and just make it so that the Listener must implement three different functions, one for each type:

class AEventListener
{
public:
    virtual ~AEventListener();

    virtual void notifyA(A payload) = 0;
    virtual void notifyB(B payload) = 0;
    virtual void notifyC(C payload) = 0;
};

More generally, it is pretty difficult in C++ to make a concept like "Function object that is callable with any particular type of arguments". This is in part because... it is not very useful, there is not much that you can do generically with data of ANY type that you can assume nothing about.

So I would suggest that you think hard about refining your Event Listener concept, and make more concrete what it is that objects of this type are ACTUALLY supposed to be required to do.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • I have gone for the base class for the payload and that seems to work from what I have tried. It's probably better to make it more concrete like you suggested but if there are a lot of events, then it could get messy with all the definitions. Can you take a quick look at my OP with my attempt? Would the casting have any performance issues? – jjmcc Feb 03 '18 at 22:49