c++中如何使用std::function_c++通用函数包装器用法详解【实例】

std::function 是可调用对象的类型擦除容器,非万能函数指针;支持函数指针、lambda、bind 等,但有开销、不可比较、移动后状态未定义。

直接说结论:std::function 不是“万能函数指针”,它本质是可调用对象的类型擦除容器,能存函数指针、lambda、绑定表达式、成员函数指针等,但有开销、不能比较相等、不支持移动后状态检查。

std::function 的声明与基本赋值场景

声明时必须显式指定签名,比如 std::function 表示接受两个 int 参数、返回 int 的可调用对象。常见错误是漏写括号或参数类型不匹配:

  • 错:std::function f = [](int a, int b) { return a + b; }; —— 这行其实对,但若 lambda 捕获了局部变量而没加 [&][=],编译失败
  • 错:std::function f = some_function_ptr;some_function_ptr 实际是 void(*)(int) —— 签名不兼容,直接编译报错
  • 正确赋值来源包括:普通函数指针无捕获 lambda(自动转为函数指针)std::bind 结果、std::mem_fn 或成员函数指针包装体

捕获型 lambda 和 std::function 的内存开销

std::function 内部用小对象优化(SOO),通常前 16–32 字节存在自身对象里;一旦 lambda 捕获大量数据(如大数组、std::stringstd::vector),就会触发堆分配。这不是 bug,但容易被忽略:

  • 小捕获(如 [x](int y){ return x + y; },其中 xint)—— 通常不堆分配
  • 大捕获(如 [data = std::vector(100000)](){ ... })—— 必然堆分配,且拷贝构造 std::function 会复制整个 vector
  • 避免方式:改用引用捕获 [&data](注意生命周期!),或把大数据抽成外部对象,只捕获指针/引用
std::vector big_data(100000, 42);
auto bad_lambda = [big_data]() { return big_data.size(); }; // 堆分配,拷贝重
auto good_lambda = [&big_data]() { return big_data.size(); }; // 不复制 data,但需确保 big_data 活得比 function 久
std::function f = good_lambda;

std::function 与成员函数指针的绑定

不能直接把成员函数指针赋给 std::function,因为缺少 this。必须绑定实例(或用 std::bind / lambda 包装):

立即学习“C++免费学习笔记(深入)”;

  • 错:std::function f = &MyClass::do_something; —— 编译失败,“no known conversion”
  • 对(lambda):MyClass obj; std::function f = [&obj]() { obj.do_something(); };
  • 对(std::bind):std::function f = std::bind(&MyClass::do_something, &obj);
  • 注意:若用 std::bind 绑定临时对象(如 std::bind(..., MyClass{})),调用时 this 悬空,UB

性能敏感场景下 std::function 的替代选择

如果函数调用在 hot path(如循环内、图形渲染每帧调用),std::function 的虚函数调用开销(即使 SOO)和可能的堆访问会影响性能。这时应考虑:

  • 模板参数化:把可调用对象作为模板参数传入,让编译器内联(最高效)
  • 函数指针:仅限无状态、无捕获的自由函数
  • 手动函子(functor)类:定义 operator(),零开销,但失去类型擦除能力
  • 别为了“统一接口”强行用 std::function —— 它的便利性是有代价的

真正麻烦的不是怎么写,而是忘记它背后是类型擦除+运行时分发,以及移动后原对象进入有效但未指定状态(不能再次调用,也不能比较)。