C++怎么使用委托_C++函数对象与回调【灵活】

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

C++怎么使用委托_C++函数对象与回调【灵活】

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。