1

Edit: TL;DR

I guess my main problem is I don't know how to store a list of functions that all take one argument, where the argument type is different between each function, but always extends from EventBase, for calling later.

i.e: EventChild extends from EventBase. A function with the signature

<void (EventChild&)> 

will not fit into a variable of type

std::function<void(EventBase&)>

How do I store functions like this, knowing that a user shouldn't have to modify the class where they are stored each time they create a new event extending from our EventBase class?

Note: I had previously been told I could use a dynamic_cast to accomplish this. I have been trying to do exactly that, but it hasn't been working. I imagine for that to work I would have to use pointers somehow, but I am new enough to C++ that I'm not sure how to do it. Maybe that should be the starting point?

One of the problems with dynamic casting pointers I have been having is 'I can convert a pointer of type:

(Subbscriber*)(getDemoEvent(EventDemo&)

to type:

void(EventBase&)

or something along those lines. (not at my computer right now to try it) This is obviously a problem limited to member functions, I assume.


I recently posted a question on here with the intention of solving an issue for a C++ Event system based on a "Publisher->Dispatcher->Subscriber" pattern. I don't know the exact name of this pattern, but I hear that it is a variant on the Observer pattern with an added "middle-man."

I have been trying to get this system to work for a while now and I am completely stuck. It was suggested in the comments of the previous question that for what I was trying to accomplish, my program layout is incorrect. This is very likely the case since I had been researching other event systems that were close to what I am after trying to modify them for use they were unintended for. So I figured I would describe what I am after, and ask the more general question of "How would you go about structuring and creating this?"

So here is my general idea of how the system should be laid out and how it should operate in a basic example:

Starting with the idea of 5 different files (plus headers and maybe some subclasses):

main.cpp
dispatcher.cpp
publisher.cpp
subscriber.cpp
eventbase.cpp

publishers and subscribers could be anything, and they only serve as an example here. The first order of business would be to create an instance of our Dispatcher class. Following that, we create instances of our publisher/subscriber classes. These 2 classes could be a part of the same file, different files, multiples of each, or not event be classes at all but simply free functions. For the sake of simplicity and testing, they are 2 separate classes that know nothing about each other. When these 2 classes are created, they should be passed a reference or pointer to our dispatcher instance. This is easy enough. Now let's get to how you should use the system.

A user of the system should be able to create a class that inherits from our EventBase class. Ideally, there should be no requirement on variables or functions to override from the base class. Let's say we have created a new event class called EventDemo, with a public const char* demoString = "I am a Demo Event";.

From our subscriber class, we should be able to tell our dispatcher that we want to listen for and receive some events. The syntax for doing so should be as simple as possible. Lets create a member function in our subscriber that looks like this:

void Subscriber::getDemoEvent(const EventDemo &ev) {
    std::cout << ev.demoString;
} 

Now we need a way to bind that member function to our dispatcher. We should probably do that in our constructor. Let's say that the reference to our dispatcher that we passed to our subscriber when we created it is just called 'dispatcher'. The syntax for subscribing to an event should look something like this:

dispatcher->subscribe("EventToSubTo", &getDemoEvent);

Now since we are in a class trying to pass a member function, this probably isn't possible, but it would work for free functions. For member functions we will probably need and override that looks like this:

dispatcher->subscribe("EventToSubTo", &Subscriber::getDemoEvent, this);

We use 'this' since we are inside the subscribers constructor. Otherwise, we could use a reference to our subscriber. Notice that I am simply using a string (or const char* in c++ terms) as my "Event Key". This is on purpose, so that you could use the same event "type" for multiple events. I.E: EventDemo() can be sent to keys "Event1" and "Event2".

Now we need to send an event. This can be done anywhere we have a reference to our dispatcher. In this case, somewhere in our publisher class. The syntax should look something like this to send our EventDemo:

dispatcher->emit("EventToSubTo", EventDemo());

Super simple. It's worth noting that we should be able to assign data to our event through it's constructor, or even template the event. Both of these cases are only valid if the event created by the user supports it. In this case, the above code would look something like this:

dispatcher->emit("EventToSubTo", EventDemo(42));

or

dispatcher->emit("EventToSubTo", EventDemo<float>(3.14159f));

It would be up to the user to create a member function to retrieve the data.

OK, so, all of that should seem pretty simple, and in fact, it is, except for one small gotcha. There are already systems out there that store functions in a map with a type of . And therein lies the problem... We can store our listener functions, as long as they accept a type of EventBase as their argument. We would then have to type cast that argument to the type of event we are after. That's not overly difficult to do, but that's not really the point. The point is can it be better. Another solution that was brought up before was the idea of having a separate map, or vector, for each type of event. That's not bad either, but would require the user to either modify the dispatcher class (which would be hard to do when this is a library), or somehow tell the dispatcher to "create this set of maps" at compile time. That would also make event templating a nightmare.

So, the overly generalized question: How do we do that?

That was probably a very long winded explanation for something seemingly simple, but maybe someone will come along not not know about it.

I am very interested to hear thoughts on this. The core idea is that I don't want the 2 communicators (publisher and subscriber) to have to know anything about each other (no pointers or references), but still be able to pass arbitrary data from one to the other. Most implementations I have seen (signals and slots) require that there be some reference to each other. Creating a "middle-man" interface feels much more flexible.

Thank you for your time.


For reference to my last question with code examples of what I have so far: Store a function with arbitrary arguments and placeholders in a class and call it later

I have more samples I could post, but I think it's highly likely that the structure of the system will have to change. Waiting to hear thoughts!

Dalen Catt
  • 111
  • 1
  • 9
  • Speaking of you initial problem, `Dispatcher` is no different from either Publisher or Subscriber. In fact, it implements both interfaces. Well, it could. In Qt, where every object can be both, if you connect one object's signal to another object's signal, the latter signal is automatically emitted as soon as the former is. – bipll Nov 03 '17 at 05:31
  • Yes, you are correct. That's not really the point of the flow though. The point is simple to create a middle-man interface, that everyone knows about and can communicate with, so that it nothing has to know about anyone else. You could technically build a middle-man interface into *any* event system (like Qt's signals & slots), but I think the implementation would be pretty clunky and not very user friendly. I'm not sure what you mean by "my initial problem" with that. That was never part of the problem. The problem was more so how to store the different types of callbacks. Could you explain? – Dalen Catt Nov 03 '17 at 05:44
  • It's not entirely clear to me what the actual problem is. The majority of your text seems to describe a system that you believe should (but doesn't?) solve the actual problem. Also, it seems to me your dispatcher is what's generally called a [broker][https://en.wikipedia.org/wiki/Message_broker] and could be left out of the question without impacting any potential solution? – AVH Nov 03 '17 at 21:52
  • Sorry. I guess my main problem is I don't know how to store a list of functions that all take one argument, where the argument type is different between each function, but always extends from EventBase, for calling later. i.e: EventChild extends from EventBase. A function with the signature void (EventChild&) will not fit into a var of type std::function. How do I store functions like this, knowing that a user shouldn't have to modify the class where they are stored each time they create a new event extending from our EventBase class. I've added this up top as a TL;DR. – Dalen Catt Nov 04 '17 at 02:20

0 Answers0