<p>结论:用 std::function + std::unordered_map 或 std::vector 就够了,除非嵌入式或老编译器需禁用 RTTI 和堆分配;此时须用函数指针数组 + void* 上下文。</p>

直接说结论:用 std::function + std::unordered_map 或 std::vector 就够了,除非你卡在嵌入式或老编译器上——那种情况必须绕开 RTTI 和堆分配,改用函数指针数组 + void* 上下文。
为什么不能直接用 std::function 存成员函数指针?
因为 std::function<void></void> 可以装普通函数、lambda、绑定后的成员函数,但**不能直接存裸的成员函数指针(如 &MyClass::onEvent)**——它类型是 void (MyClass::*)(int),和 void(*)(int) 不兼容。常见错误现象是编译失败,报错类似:
error: no matching function for call to 'std::function<void(int)>::function(void (MyClass::*)(int))'
解决办法只有两个:
- 用
std::bind绑定对象实例:std::bind(&MyClass::onEvent, &obj, std::placeholders::_1) - 用 lambda 捕获对象:
[&obj](int x) { obj.onEvent(x); } - 或者——如果对象生命周期难保证,就别捕获裸指针,改用
std::shared_ptr+weak_ptr做弱引用管理
Signal<Args> 的线程安全边界在哪?
多数轻量实现(比如用 std::vector<std::function<...>> 的版本)只在 emit() 时加锁,但 connect() 和 disconnect() 同样可能被并发调用。一旦出现“边遍历边删除”,就会触发未定义行为——std::vector::erase() 失效迭代器,std::remove() 也不安全。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 若只在单线程注册/断开,
emit()加锁就够了 - 若需多线程动态管理连接,必须对整个槽容器加互斥(
std::mutex),且disconnect()不能靠值比较(std::function不可判等),得用 ID 管理(如std::unordered_map<int, std::function<...>>) - 避免在槽函数里调用
disconnect()自身——这会导致容器迭代中断,应改用延迟清理(标记+后续 sweep)
嵌入式环境必须手写函数指针数组?
是的。当编译器是 GCC 4.6、MSVC 2010 或裸机 ARM-GCC,且禁用 RTTI/异常/std::function 时,std::function 不可用,虚函数表又太重,唯一可行路径就是函数指针数组 + 静态包装器。
核心约束:
- 所有槽签名必须统一为
void(*)(void*, int)这类固定形式 - 对象指针通过
void*传入,由静态函数中转调用成员函数:static void wrapper(void* obj, int arg) { static_cast<MyClass*>(obj)->onEvent(arg); } - 调用方负责确保
obj在整个信号生命周期内有效——没智能指针,没自动解绑,全靠人工生命周期管理 - 数组大小必须编译期确定或手动扩容,运行时不能
new,否则违反“零分配”要求
真正容易被忽略的点不是语法,而是槽的生命周期绑定方式:现代实现靠 shared_ptr 自动解绑,嵌入式实现靠程序员记住“谁注册谁注销”,漏掉一个,就是悬垂调用。


















