c++中std::function+std::bind是最轻量委托实现,但需严控对象生命周期;函数指针零开销但无状态捕获能力;std::function有类型擦除开销,高频场景应避免反复构造;信号-槽机制非委托替代品,适用多播而非单目标调用。

std::function + std::bind 能直接当委托用,但要注意对象生命周期
委托本质是「把函数调用延迟并解耦到另一处执行」,C++ 没原生 delegate 关键字,但 std::function 配合 std::bind 或 lambda 就是最常用、最轻量的实现方式。它不依赖第三方库,C++11 起就可用。
常见错误是绑定成员函数时传入了临时或已销毁对象的 this 指针,导致调用时崩溃或未定义行为——比如在回调里访问 this->data,但对象早被析构了。
- 用
std::shared_ptr管理被委托对象的生命周期,再用std::weak_ptr在 lambda 里捕获,调用前lock()判断是否还活着 - 避免直接
std::bind(&Class::func, this, ...),尤其当this所指对象生命周期不可控时 - 如果委托只用于同一线程且生命周期明确(如 GUI 控件回调绑定到窗口对象),直接捕获
this是可接受的,但得确保调用点不会晚于对象销毁
auto cb = [ptr = shared_from_this()](int x) {
if (auto p = ptr.lock()) {
p->handle(x);
}
};
用函数指针模拟 C 风格委托,但无法捕获状态
纯函数指针(如 void (*)(int))是最底层的委托形式,零开销、兼容 C ABI,适合嵌入式或与 C 库对接(比如注册 qsort 的比较函数)。但它不能带捕获,也不能指向成员函数——这是硬限制,不是写法问题。
容易踩的坑是试图把 lambda(哪怕没捕获)直接转成函数指针:只有无捕获 lambda 才能隐式转,且必须类型严格匹配;一旦加了 [=] 或 [&],编译就报错 cannot convert lambda to function pointer。
立即学习“C++免费学习笔记(深入)”;
- 需要状态时,必须额外传一个
void*上下文参数(如 POSIXqsort的第 4 个参数),由使用者自己做类型转换和生命周期管理 - 成员函数想塞进去?得写一个静态包装器,把
void*强转回对象指针再调用,手动维护对象存活 - 性能上比
std::function略高(无类型擦除开销),但代码更脆、易出错
std::function 的类型擦除有开销,高频调用场景要测
std::function 内部用类型擦除实现多态,每次构造、拷贝、调用都有间接成本。在游戏循环、音频处理或每秒数万次的事件分发中,这个开销可能从纳秒级变成微秒级,积少成多。
不是所有场景都敏感,但如果你发现 profiler 里 std::function::operator() 占比异常高,就得警惕。这时候别急着换方案,先确认是不是误用了拷贝(比如反复传值而非 const 引用)或过度封装。
- 高频路径上,优先用函数指针或模板参数(如策略类模板)替代
std::function - 避免在循环内反复构造
std::function对象;提前存为成员变量或局部静态变量 - Clang/GCC 下开启
-O2后,简单 lambda 的std::function调用常能内联,但绑定成员函数 + 捕获变量后基本无法内联
信号-槽机制(如 libsigc++ 或 Qt)不是委托,别混用
Qt 的 connect 或 libsigc++ 的 signal 是多播(multiple subscribers)、带连接管理、支持跨线程的信号系统,而委托是单目标、一对一的调用包装。强行用信号当委托,等于用卡车送快递——功能过剩,还多一堆引用计数、线程队列、元对象系统开销。
典型误用:给每个按钮点击写一个 Signal<void></void>,只为绑定一个 handler。这时 std::function<void></void> 更直白、内存更紧凑、析构更快。
- 需要广播、断连、自动清理(如对象销毁时自动断开所有槽)才上信号系统
- Qt 中
QObject::connect绑定普通函数或 lambda 时,底层仍走std::function类似逻辑,但多了元信息注册和事件循环调度 - 跨线程投递信号会触发队列+事件循环,而委托调用永远是同步的,这点必须清楚
委托真正的复杂点不在语法,而在谁拥有调用权、谁负责生命周期、调用栈深度是否可控——这些没法靠模板推导或编译器检查,全靠设计时想清楚。写完记得问一句:这个回调,到底该在哪儿析构?









