C++中用std::function兼容不同函数签名实现类C#事件系统的问题
问题核心
你说得对,想用单个std::function来存储任意签名的函数其实是行不通的——因为std::function本身是签名绑定的,它的模板参数就固定了它能接受的可调用对象的参数和返回值类型。比如std::function<void()>只能存储无参、返回值为void的可调用对象,一旦签名不匹配,编译器就会报错。
为什么第三个绑定失败?
看你给出的代码:
std::function<void()> fn = std::bind(FuncWithParam, _1);
这里std::bind(FuncWithParam, _1)生成的可调用对象是需要接收一个const std::string&参数的(_1是占位符,代表调用时要传入第一个参数),但你把它赋值给了无参的std::function<void()>,两者的签名完全不匹配,编译器自然会拒绝这种赋值。
可行的解决方案
要实现支持任意签名的C++事件系统,你需要用更灵活的类型处理方式,下面是几种常用方案:
1. 用std::variant存储多签名函数
如果你能提前枚举所有需要支持的函数签名,可以用std::variant来包装不同类型的std::function,调用时通过std::visit匹配类型:
#include <variant> #include <functional> #include <string> #include <iostream> // 定义支持的所有事件函数签名 using EventFunc = std::variant< std::function<void()>, std::function<void(const std::string&)> >; // 通用调用函数 void invokeEvent(EventFunc& func) { std::visit([](auto&& f) { using FuncType = std::decay_t<decltype(f)>; if constexpr (std::is_same_v<FuncType, std::function<void()>>) { f(); } else if constexpr (std::is_same_v<FuncType, std::function<void(const std::string&)>>) { f("Triggered with param"); } }, func); } // 测试函数 void Func() { std::cout << "Notified (no param)" << std::endl; } void FuncWithParam(const std::string& str) { std::cout << "Received: " << str << std::endl; } int main() { EventFunc fn1 = std::bind(Func); EventFunc fn2 = std::bind(FuncWithParam, "Pre-bound message"); EventFunc fn3 = FuncWithParam; // 直接存储带参函数 invokeEvent(fn1); invokeEvent(fn2); invokeEvent(fn3); return 0; }
2. 基于类型擦除的通用事件系统
如果要支持完全任意的签名,可以自己实现类型擦除的事件槽:
#include <memory> #include <functional> #include <string> #include <iostream> #include <vector> // 基类:统一所有事件槽的接口 class EventSlot { public: virtual ~EventSlot() = default; }; // 派生类:针对特定签名的事件槽 template<typename Signature> class TypedEventSlot : public EventSlot { public: using FuncType = std::function<Signature>; TypedEventSlot(FuncType func) : func_(std::move(func)) {} template<typename... Args> void invoke(Args&&... args) { func_(std::forward<Args>(args)...); } private: FuncType func_; }; // 事件类:管理所有订阅的事件槽 class Event { public: // 添加任意签名的事件处理函数 template<typename Signature, typename Func> void subscribe(Func&& func) { slots_.emplace_back(std::make_unique<TypedEventSlot<Signature>>(std::forward<Func>(func))); } // 触发事件,传入对应参数 template<typename... Args> void trigger(Args&&... args) { for (auto& slot : slots_) { // 尝试转换为对应签名的事件槽 auto* typedSlot = dynamic_cast<TypedEventSlot<void(Args...)>*>(slot.get()); if (typedSlot) { typedSlot->invoke(std::forward<Args>(args)...); } } } private: std::vector<std::unique_ptr<EventSlot>> slots_; }; // 测试函数 void Func() { std::cout << "Notified (no param)" << std::endl; } void FuncWithParam(const std::string& str) { std::cout << "Received: " << str << std::endl; } int main() { Event myEvent; myEvent.subscribe<void()>(Func); myEvent.subscribe<void(const std::string&)>(FuncWithParam); myEvent.subscribe<void(const std::string&)>(std::bind(FuncWithParam, "Bound message")); // 触发无参事件(只有Func响应) myEvent.trigger(); // 触发带参事件(两个带参函数响应) myEvent.trigger("Hello from trigger"); return 0; }
3. 贴近C#风格的统一签名方案
如果想更贴近C#的事件模型,通常会统一事件处理函数的签名,比如定义一个包含事件参数的基类,让所有事件处理函数都接收这个参数对象:
#include <functional> #include <string> #include <iostream> #include <vector> // 事件参数基类 class EventArgs { public: virtual ~EventArgs() = default; }; // 带字符串参数的事件参数 class StringEventArgs : public EventArgs { public: std::string message; StringEventArgs(const std::string& msg) : message(msg) {} }; // 统一的事件处理函数签名 using EventHandler = std::function<void(EventArgs*)>; // 事件类 class Event { public: void addHandler(EventHandler handler) { handlers_.push_back(std::move(handler)); } void trigger(EventArgs* args) { for (auto& handler : handlers_) { handler(args); } } private: std::vector<EventHandler> handlers_; }; // 测试函数 void Func(EventArgs*) { std::cout << "Notified (no param)" << std::endl; } void FuncWithParam(EventArgs* args) { if (auto strArgs = dynamic_cast<StringEventArgs*>(args)) { std::cout << "Received: " << strArgs->message << std::endl; } } int main() { Event myEvent; myEvent.addHandler(Func); myEvent.addHandler(FuncWithParam); myEvent.trigger(nullptr); // 触发无参事件 myEvent.trigger(new StringEventArgs("Hello C# style")); // 触发带参事件 return 0; }
内容的提问来源于stack exchange,提问作者Manspider
相关产品推荐
相关产品推荐

