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

C++:如何实现任意类成员函数传递至另一类作为回调

实现基于成员函数回调的字段变更通知器

我正在编写一个模板类,用于为应用组件实现字段变更监听功能——组件可以监听被观察对象中单个字段的变化,字段用枚举类型表示。目前已经实现了一个基于Listener接口的简单通知器:

template <typename EFieldsEnum>
class AbstractNotifier
{
public:
    void addListener(EFieldsEnum listenedField, INotifierListener<EFieldsEnum>* listener)
    {
        if (_listeners.find(listenedField) == _listeners.cend())
        {
            std::unordered_set<INotifierListener<EFieldsEnum>*> listeners;
            listeners.insert(listener);
            _listeners.emplace(listenedField, listeners);
        }
        else
        {
            _listeners.find(listenedField)->second.insert(listener);
        }
    }

    void notify(EFieldsEnum updatedField)
    {
        for (INotifierListener<EFieldsEnum>* listener : _listeners[updatedField])
        {
            listener->onUpdate(updatedField);
        }
    }

protected:
    std::unordered_map<EFieldsEnum, std::unordered_set<INotifierListener<EFieldsEnum>*>> _listeners;
};

现在我想修改实现,让监听组件无需实现特定的Listener接口,也不用在onUpdate里写switch分支,而是直接传递成员函数作为回调注册,比如:

// Component1的注册方式
notifier.addListener(FieldEnum::MyField1, this, &Component1::onMyField1Updated);

// Component2的注册方式
notifier.addListener(FieldEnum::MyField1, this, &Component2::someOtherMethod);

Component1和Component2不需要继承公共接口。请问这种实现是否可行?我尝试过用基类+成员函数指针的方式,但无法泛型表示不同组件的成员函数类型,尝试的代码如下:

class IListener {};

void (IListener::*ListenerCallback)();

template <typename EFieldsEnum>
class AbstractNotifier
{
public:
    void addListener(EFieldsEnum listenedField, IListener* listener, ListenerCallback listenerCallback)
    {
        std::pair<IListener*, ListenerCallback> pairToAdd = std::make_pair<IListener*, ListenerCallback>(listener, ListenerCallback);
        if (_listeners.find(listenedField) == _listeners.cend())
        {
            std::unordered_set<std::pair<IListener*, ListenerCallback>> listenersAndCallbacks;
            listenersAndCallbacks.insert(pairToAdd);
            _listeners.emplace(listenedField, listenersAndCallbacks);
        }
        else
        {
            _listeners.find(listenedField)->second.insert(pairToAdd);
        }
    }

    void notify(EFieldsEnum updatedField)
    {
        for (const std::pair<IListener*, ListenerCallback>& listenerAndCallback : _listeners[updatedField])
        {
            IListener* listener = listenerAndCallback.first;
            ListenerCallback listenerCallback = listenerAndCallback.second;
            listener->*listenerCallback();
        }
    }

protected:
    std::unordered_map<EFieldsEnum, std::unordered_set<std::pair<IListener*, ListenerCallback>>> _listeners;
};

解决方案

完全可行,核心是用类型擦除统一不同组件的成员函数回调类型,推荐使用std::function<void()>包装绑定后的回调对象,实现任意可调用类型的存储(包括绑定了对象的成员函数)。

修改后的AbstractNotifier实现如下:

#include <unordered_map>
#include <vector>
#include <functional>
#include <utility>

template <typename EFieldsEnum>
class AbstractNotifier
{
public:
    // 模板化addListener,适配任意类型的对象和成员函数
    template <typename T>
    void addListener(EFieldsEnum listenedField, T* obj, void (T::*callback)())
    {
        // 将对象与成员函数绑定为无参可调用对象
        auto boundCallback = std::bind(callback, obj);
        // 存入对应字段的回调列表
        _listeners[listenedField].push_back(std::move(boundCallback));
    }

    void notify(EFieldsEnum updatedField)
    {
        auto it = _listeners.find(updatedField);
        if (it == _listeners.end())
            return;
        
        // 遍历调用所有回调
        for (auto& callback : it->second)
        {
            callback();
        }
    }

protected:
    // 每个字段对应一组无参回调函数
    std::unordered_map<EFieldsEnum, std::vector<std::function<void()>>> _listeners;
};

关键说明

  • 模板化addListener:通过模板参数T适配任意组件类型,无需强制继承公共接口。
  • std::bind:将组件对象指针与成员函数绑定为无参可调用对象,实现类型擦除。
  • std::function<void()>:作为统一存储类型,兼容所有绑定后的回调,解决不同成员函数类型无法统一存储的问题。
  • vector替代unordered_set:std::function没有默认哈希函数,无法直接存入unordered_set,用vector存储更简单;若需去重,可额外维护对象+回调的映射避免重复注册。

使用示例

#include <iostream>

enum class FieldEnum { MyField1, MyField2 };

class Component1
{
public:
    void onMyField1Updated()
    {
        std::cout << "Component1: MyField1 updated\n";
    }
};

class Component2
{
public:
    void someOtherMethod()
    {
        std::cout << "Component2: MyField1 updated\n";
    }
};

int main()
{
    AbstractNotifier<FieldEnum> notifier;
    Component1 c1;
    Component2 c2;

    notifier.addListener(FieldEnum::MyField1, &c1, &Component1::onMyField1Updated);
    notifier.addListener(FieldEnum::MyField1, &c2, &Component2::someOtherMethod);

    notifier.notify(FieldEnum::MyField1);
    // 输出:
    // Component1: MyField1 updated
    // Component2: MyField1 updated
    return 0;
}

内容的提问来源于stack exchange,提问作者Dracoflamme

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 03:25:13