You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

订阅者-发布者模式中成员函数指针调用报错的解决咨询

订阅者-发布者模式中成员函数指针调用错误的解决

问题概述

尝试实现订阅者-发布者模式,基类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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:52:52