std::function_ref 是零开销、非拥有、类型擦除的只读回调视图,内部仅存 void* 和函数指针,不分配内存、不复制可调用体,但不管理生命周期,误用将导致悬垂引用或未定义行为。

std::function_ref 是类型擦除的只读视图,不是函数引用
很多人看到 std::function_ref 就以为它是类似 int& 那样的原生引用,其实完全不是。它不持有任何对象,也不参与所有权管理,只是一个轻量级的、非拥有的、类型擦除的回调视图。它的核心目标是:在不分配内存、不复制可调用体的前提下,把任意可调用物(lambda、函数指针、std::function、绑定对象等)统一接入同一接口。
为什么不能直接用 T& 或 const T& 代替?
普通引用要求编译期知道具体类型,而回调场景往往需要“接受多种可调用类型”的统一参数签名。比如一个日志函数想同时接受 void()、void(int)、const char*() 等不同签名的可调用体——这无法用单个模板参数 T& 实现,因为每个 T 都是不同类型,函数重载或模板推导会爆炸。
-
std::function_ref可绑定[]{}、&my_free_func、std::function、甚至捕获了局部变量的 lambda(只要不逃逸) - 而
auto&& f或const auto& f虽然也能转发,但无法作为函数参数统一声明;写成模板又导致实例化膨胀 - 用
std::function会触发堆分配(除非小对象优化生效),且拷贝有开销;std::function_ref完全避免这两点
std::function_ref 的零开销怎么来的?
它内部只存两个字段:一个指向可调用体的 void*(或类似指针),一个指向调用分发函数的函数指针。两者加起来通常就是 16 字节(x64),且所有操作都是纯指针解引用 + 间接跳转,无虚函数表、无 new/delete、无异常传播开销。
void example(std::function_refcb) { int result = cb(3.14); // 直接调用,无额外分支或检查 }
- 传入的 lambda 若无捕获,
cb内部存储的是其地址 + 编译器生成的静态调用桩 - 传入带捕获的 lambda,
cb存储的是捕获块地址 + 对应的调用桩(该桩由标准库为每种签名生成一次) - 传入
std::function,cb仅借用其存储区和调用逻辑,不复制内容 - 注意:
std::function_ref不延长所引用对象的生命期——若传入栈上 lambda 并保存其function_ref到作用域外,就是悬垂引用
常见误用:把它当 std::function 用或跨作用域保存
最典型的坑是把它当成“轻量版 std::function”来长期持有回调。它没有所有权语义,也没有移动/拷贝构造的安全保障(拷贝是浅复制,移动未定义)。
立即学习“C++免费学习笔记(深入)”;
- 不要把
std::function_ref成员变量存进类里,除非你 100% 控制被引用对象的生命周期长于该类 - 不要从函数返回
std::function_ref,除非返回的是全局函数或静态 lambda - 不能对临时 lambda 写
std::function_ref{[]{}}()—— 临时对象在表达式结束就销毁,引用立刻悬垂 - 它不支持
nullptr检查(没有空状态),也不能赋值(只有构造)











