0

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);
}
5
  • (i->*h)(); -> h() ... no need for i. Or just subscription->handler(); In your auto h = subscription->handler;, the deduced type for h is Subscriber::Handler, so no need for a cast. Or have I totally missed the point, here? Commented Apr 8, 2023 at 19:50
  • Or, if you need the instance in the handler function, you can add that as an argument: typedef void (*Handler)(Subscriber*); and then call in your Notify like h(i);. Maybe we need more details here, like a sample of usage code? Commented Apr 8, 2023 at 19:57
  • As it stands, your code doesn't declare handler as a member function; it is a data member whose type is a function pointer (to a free function). Commented Apr 8, 2023 at 19:59
  • why do you have a vector of pointers? Commented Apr 8, 2023 at 20:24
  • @AdrianMole I added an example to the question, take a look, please Commented Apr 9, 2023 at 7:21

2 Answers 2

0

1.Maybe you don't need Subscription.like this

class Subscriber {
public:
  typedef void (*Handler)();
  Handler handler;
};

2.you just need to call handle() in Notify().like this

virtual void Notify() {
  for (auto &subscriber : this->subscribers) {
    subscriber->handler();
  }
};
Sign up to request clarification or add additional context in comments.

1 Comment

Your example doesn't fit my needs, you call the handler by its name. But my goal is to make a generic function type, so I could pass any function implementing this type to a Publisher. I updated the question and added a example of behaviour I want to achieve. Take a look, please
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.