std::function能接住签名匹配的函数指针、lambda(含捕获)、成员函数指针(需bind或lambda包装)、仿函数;但不延长捕获变量生命周期,有类型擦除开销,慎用于高频路径。

std::function 能接住哪些东西
它不是万能胶,但比裸函数指针灵活得多。std::function 的模板参数决定了它能绑定什么签名的可调用对象。比如 std::function<void></void> 只接受能以一个 int 参数调用、返回 void 的东西。
- 普通函数指针:直接赋值,无开销
- lambda(带捕获或不带捕获):按值拷贝,注意捕获大对象可能隐式复制
- 成员函数指针 + 对象指针/引用:必须用
std::bind或 lambda 包一层,否则类型不匹配 - 重载了
operator()的类实例(仿函数):只要签名对得上就行
常见错误是传入一个未捕获变量的 lambda 给异步回调,结果回调执行时变量已析构——std::function 会拷贝 lambda,但不会延长它捕获的栈变量生命周期。
用 lambda 捕获 this 时怎么避免悬空指针
在类成员里存一个 std::function 回调,又想在回调里访问成员变量,最简写法是 [this]() { ... },但这很危险:如果回调被异步调度,而对象提前销毁,this 就成野指针了。
- 安全做法是捕获
shared_ptr<this_type></this_type>:前提是类继承自std::enable_shared_from_this<t></t>,然后写[self = shared_from_this()]() { self->do_something(); } - 如果不能改类基类,就别存回调,改用一次性注册 + 显式取消机制(比如返回
std::unique_ptr<callback_handle></callback_handle>) - 绝对不要捕获局部变量的原始指针或引用进异步回调,哪怕看起来“生命周期够长”
错误示例:[ptr = &data](){ use(*ptr); } —— data 是栈变量,回调执行时大概率已出作用域。
立即学习“C++免费学习笔记(深入)”;
std::function 性能代价在哪
它内部有类型擦除,每次调用都有一次虚函数跳转(或类似机制),比直接调用函数指针慢一点;而且每个实例至少占 16–32 字节(取决于平台和编译器),比裸函数指针(8 字节)胖得多。
- 高频热路径(如每帧调用数百次的渲染回调)慎用,优先考虑模板参数化或函数指针
- 作为事件总线、配置项回调、测试桩(stub)等低频场景,这点开销完全可接受
- Clang 和 GCC 在 -O2 下对无捕获 lambda 的
std::function构造有时能优化掉部分开销,但调用开销仍在
如果你看到 profiler 里 std::function::operator() 占比异常高,先检查是不是把它塞进了 tight loop 里。
替代方案:什么时候该用 std::function,什么时候该绕开
它适合“回调类型不确定但签名固定”的场景,比如 GUI 按钮点击、网络请求完成通知。但如果只有两三种固定行为,枚举 + switch 更轻量;如果只有一种行为且不需运行时替换,模板参数更零成本。
- 需要运行时决定调用哪个逻辑 → 用
std::function - 编译期就知道调用目标,且只有一两个 → 用函数模板或重载
- 要传递成员函数且不想写
std::bind或 lambda → 写个包装函数对象,显式存T*和void (T::*)(),自己控制生命周期
真正容易被忽略的是:std::function 的移动构造是 noexcept 的,但拷贝构造不是——往容器里 push_back 一个带大捕获的 std::function,可能触发异常,而你根本没写 try/catch。










