I'm trying to implement subscriber-publisher pattern. My base class Subscriber
doesn't have a listener method, it declares type Handler
instead. The idea behind this is that a derived class will be able to have multiple handlers which will implement this type and can be passed to a Publisher
. I'm trying to invoke a handler in Notify
method, but I'm getting an error which states expression must have pointer-to-member type
. How can I cast my function pointer to a member type and is it even possible with my approach?
class Subscriber {
public:
typedef void (*Handler)();
};
struct Subscription {
Subscriber *instance;
Subscriber::Handler handler;
};
class Publisher {
protected:
std::vector<Subscription> subscriptions;
public:
virtual void AddSubscriber(Subscription subscription) {
this->subscriptions.push_back(subscription);
};
virtual void Notify() {
for (auto subscription : this->subscriptions) {
auto i = subscription.instance;
auto h = subscription.handler;
(i->*h)();
}
};
};
My goal is to make an expandable system to be able to do something like this
struct KeyboardSubscription : Subscription {
Event event_type;
Key listened_key;
}
class GUI : Subscriber {
void OpenMenu() { ... }; // implementing Subscriber::Handler
void CloseMenu() { ... }; // implementing Subscriber::Handler too
}
class Keyboard : Publisher {
void Notify() {
for (KeyboardSubscription subscription : this->subscriptions) {
auto i = subscription.instance;
auto h = subscription.handler;
if (subscription.event_type == this->event_type &&
subscription.listened_key == this->pressed_key)
(i->*h)();
}
}
}
int main() {
// init GUI and Keyboard...
KeyboardSubscription sub1;
sub1.instance = gui_instance;
sub1.handler = &GUI::OpenMenu;
sub1.event_type = Key_Down;
sub1.listened_key = Enter;
KeyboardSubscription sub2;
sub2.instance = gui_instance;
sub2.handler = &GUI::CloseMenu;
sub2.event_type = Key_Down;
sub2.listened_key = Esc;
keyboard_instance.AddSubscription((Subscription)sub1);
keyboard_instance.AddSubscription((Subscription)sub2);
}
I've achieved what I wanted. I just needed to declare a function type like this:
class Subscriber {
public:
typedef void (Subscriber::*Handler)();
};
Here's a full example which compiles without any warnings and works as expected
#include <iostream>
#include <vector>
class Subscriber {
public:
typedef void (Subscriber::*Handler)();
};
struct Subscription {
Subscriber *instance;
Subscriber::Handler handler;
};
class Publisher {
protected:
std::vector<Subscription*> subscriptions;
public:
void AddSubscription(Subscription *subscription) {
this->subscriptions.push_back(subscription);
}
virtual void Notify() {
for (auto subscription : this->subscriptions) {
auto i = subscription->instance;
auto h = subscription->handler;
(i->*h)();
}
}
};
struct KeyboardSubscription : Subscription {
int event_type;
int listened_key;
};
class GUI : Subscriber {
public:
void OpenMenu() { // implementing Subscriber::Handler
std::cout << "Open menu" << std::endl;
}
void CloseMenu() { // implementing Subscriber::Handler too
std::cout << "Close menu" << std::endl;
}
};
class Keyboard : Publisher {
private:
int event_type;
int pressed_key;
public:
void AddSubscription(Subscription *subscription) {
Publisher::AddSubscription(subscription);
}
void Notify() {
for (auto sub : this->subscriptions) {
KeyboardSubscription* subscription = static_cast<KeyboardSubscription*>(sub);
auto i = subscription->instance;
auto h = subscription->handler;
if (subscription->event_type == this->event_type &&
subscription->listened_key == this->pressed_key)
(i->*h)();
}
}
void Event1() {
this->event_type = 10;
this->pressed_key = 1;
this->Notify();
}
void Event2() {
this->event_type = 20;
this->pressed_key = 2;
this->Notify();
}
};
int main() {
GUI *gui_instance = new GUI;
Keyboard *keyboard_instance = new Keyboard;
KeyboardSubscription *sub1 = new KeyboardSubscription;
sub1->instance = (Subscriber*)gui_instance;
sub1->handler = (Subscriber::Handler)(&GUI::OpenMenu);
sub1->event_type = 10;
sub1->listened_key = 1;
KeyboardSubscription *sub2 = new KeyboardSubscription;
sub2->instance = (Subscriber*)gui_instance;
sub2->handler = (Subscriber::Handler)(&GUI::CloseMenu);
sub2->event_type = 20;
sub2->listened_key = 2;
keyboard_instance->AddSubscription((Subscription*)sub1);
keyboard_instance->AddSubscription((Subscription*)sub2);
keyboard_instance->Event1();
keyboard_instance->Event2();
}
And the output is as follows:
Open menu
Close menu