std::function 是一个类型擦除的类模板,可存储任意符合签名的可调用对象(如lambda、函数指针、bind表达式、仿函数),但不支持重载函数名直接赋值,需注意空状态检查与捕获生命周期。

std::function 是什么类型,能存哪些东西
它是个类型擦除容器,本质是「能装任意符合签名的可调用对象」的通用包装器。不是函数指针,也不是模板别名,而是一个类模板实例化出来的具体类型。
- 能存
lambda(带捕获或不带捕获)、std::bind表达式、普通函数指针、重载了operator()的仿函数类实例 - 不能直接存重载函数名(比如多个
void f(int)和void f(double)),编译器无法推导签名;得显式转型,例如static_cast<void>(f)</void> - 存储成本比裸函数指针高:通常含一个指针 + 一小段控制块(小对象优化可能避免堆分配),但比
std::any或虚函数调用轻量
怎么声明和赋值,常见编译错误
声明必须写清楚调用签名,比如 std::function<int const std::string></int> —— 括号里是参数列表,前面是返回类型。漏掉 & 或 const 就可能匹配失败。
- 赋值时如果右边类型不匹配,报错信息通常是
no matching constructor或cannot convert … to …,而不是“类型不兼容”这种直白提示 - 捕获局部变量的 lambda 不能赋给
std::function并在作用域外调用——会悬垂引用;要确保捕获的对象生命周期足够长,或者改用值捕获([=])且对象可拷贝 -
std::function默认可空,构造时不初始化就是空状态,调用前必须检查if (f),否则触发std::bad_function_call
性能开销在哪,什么时候该避免用
每次调用都要经过一层间接跳转(类似虚函数),还有小对象优化判断逻辑。对高频路径(比如循环体内每帧调用)有实测影响,尤其在嵌入式或游戏引擎关键路径中。
- 若只存无捕获 lambda 或函数指针,且签名固定,直接用函数指针(
void(*)(int))或模板参数推导更高效 - 若需要运行时切换行为但又怕开销,可考虑
std::variant<:function>, std::function<...>></...></:function>配合std::visit,把部分分支转为静态分发 - 移动语义可用:
std::function支持移动构造/赋值,传参时优先用std::move(f)避免内部深拷贝(尤其当它内部已分配堆内存时)
和 std::bind、lambda、函数指针的实际选择对比
三者不是替代关系,而是适用场景不同:函数指针最轻但无法捕获;lambda 最灵活但类型唯一、无法直接作为参数类型;std::function 是折中方案,牺牲一点性能换统一接口。
立即学习“C++免费学习笔记(深入)”;
-
std::bind现在基本被 lambda 取代,除非你要绑定右值引用或做嵌套绑定;但std::bind返回对象可直接赋给std::function,而某些复杂 lambda(如含模板参数的)可能无法隐式转换 - 如果你要存的东西生命周期明确、调用频次低(比如事件回调、配置回调),
std::function省心;反之,若追求极致性能或编译期确定性,就绕过它 - 注意:
std::function不支持 SFINAE 友好检测(比如is_invocable可用,但std::function本身不能参与模板推导约束),写泛型代码时别把它当“万能可调用类型”往 concept 里塞
真正容易被忽略的是空状态处理和捕获生命周期——这两个问题在线上环境爆出来时,往往表现为偶发 crash 或未定义行为,而不是编译报错。









