订阅者-发布者模式中成员函数指针调用报错的解决咨询
订阅者-发布者模式中成员函数指针调用错误的解决
问题概述
尝试实现订阅者-发布者模式,基类Subscriber声明Handler类型,允许派生类提供多个处理函数注册到Publisher。但在Notify中调用处理函数时,出现错误expression must have pointer-to-member type,需要解决函数指针与成员类型的转换问题,并确认当前实现思路的可行性。
错误原因
你定义的Handler是普通全局/静态函数指针:
typedef void (*Handler)();
但实际要存储的是Subscriber派生类的非静态成员函数指针,这两种类型完全不兼容。成员函数指针需要绑定类实例才能调用,语法上要求(实例->*成员指针)(),而普通函数指针直接调用即可,所以编译器会报错。
解决方案
1. 修正Handler类型定义
将Handler改为Subscriber的成员函数指针类型:
class Subscriber { public: // 定义为Subscriber的成员函数指针 typedef void (Subscriber::*Handler)(); };
2. 修正订阅注册与调用逻辑
派生类的成员函数指针可以安全转换为基类的成员函数指针(前提是public继承,你的示例中GUI : Subscriber是默认private继承,需要改为public)。
3. 修正目标示例中的代码问题
- 继承方式改为
public,确保成员指针转换合法 - 调整
Keyboard类的容器类型,匹配扩展后的KeyboardSubscription - 统一订阅添加函数的命名,与基类保持一致
改进后的完整代码
核心类修正
#include <vector> class Subscriber { public: // 改为成员函数指针类型 typedef void (Subscriber::*Handler)(); }; struct Subscription { Subscriber* instance; Subscriber::Handler handler; }; class Publisher { protected: std::vector<Subscription> subscriptions; public: virtual void AddSubscriber(Subscription subscription) { subscriptions.push_back(subscription); }; virtual void Notify() { for (auto& subscription : subscriptions) { Subscriber* i = subscription.instance; Subscriber::Handler h = subscription.handler; // 成员函数指针的正确调用语法 (i->*h)(); } }; };
扩展系统示例修正
// 假设已定义Event和Key枚举 enum class Event { Key_Down }; enum class Key { Enter, Esc }; struct KeyboardSubscription : Subscription { Event event_type; Key listened_key; }; // 改为public继承 class GUI : public Subscriber { public: // 处理函数需设为public,否则无法获取地址 void OpenMenu() { /* ... */ }; void CloseMenu() { /* ... */ }; }; class Keyboard : public Publisher { private: Event event_type; Key pressed_key; // 存储扩展后的KeyboardSubscription std::vector<KeyboardSubscription> subscriptions; public: // 重写添加订阅函数,接收扩展类型 void AddSubscriber(KeyboardSubscription subscription) { subscriptions.push_back(subscription); }; void Notify() { for (auto& subscription : subscriptions) { Subscriber* i = subscription.instance; Subscriber::Handler h = subscription.handler; if (subscription.event_type == event_type && subscription.listened_key == pressed_key) { (i->*h)(); } } } }; int main() { GUI gui_instance; Keyboard keyboard_instance; KeyboardSubscription sub1; sub1.instance = &gui_instance; // 派生类成员指针隐式转换为基类成员指针 sub1.handler = &GUI::OpenMenu; sub1.event_type = Event::Key_Down; sub1.listened_key = Key::Enter; KeyboardSubscription sub2; sub2.instance = &gui_instance; sub2.handler = &GUI::CloseMenu; sub2.event_type = Event::Key_Down; sub2.listened_key = Key::Esc; keyboard_instance.AddSubscriber(sub1); keyboard_instance.AddSubscriber(sub2); }
思路可行性分析
当前思路是可行的:
- 基于成员函数指针的订阅模式属于静态类型绑定,性能开销小,适合对性能敏感的场景
- 允许派生类提供多个处理函数,完全符合你的设计意图
- 可以通过继承
Subscription扩展订阅信息(如示例中的KeyboardSubscription),保持系统扩展性
如果需要更灵活的方案(比如支持自由函数、Lambda、带参数的处理函数),可以考虑用std::function<void()>替代成员函数指针,结合std::bind或Lambda绑定实例和成员函数,代码会更简洁灵活,但会带来轻微的运行时开销。
内容的提问来源于stack exchange,提问作者user8513712
相关产品推荐
相关产品推荐

