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
相关产品推荐
相关产品推荐

