跳转至

std::function

标准 难度 状态 分级 证据 审阅 最后校验
C++11 2/5 final A 3 条(manual 3) 单审(zyuhao7) 2026-10-30

编译器支持:GCC 4.4+ · Clang 3.1+ · MSVC 16.0+。版本查询:cppreference 编译器支持。

主张与证据

主张 支持证据
用 std::function 接收异步响应回调并向下游转移 ev-drogon-function-callback-001
用 std::function 保存并调用每条日志的回调 ev-spdlog-function-callback-sink-002
用 std::function 接收遍历回调并统一应用 ev-abseil-function-flags-003

本站证据页:ev-abseil-function-flags-003 · ev-drogon-function-callback-001 · ev-spdlog-function-callback-sink-002

解决什么问题

回调需要在运行时才知道具体是可捕获状态的 lambda、函数指针还是带状态的函数对象。裸函数指针无法携带捕获;模板参数会把可调用类型写进周围所有类型,造成代码膨胀并逼着接口暴露实现细节;虚函数接口要给每个回调手写一个子类。

std::function 是一个类型擦除的“可调用对象容器”:只要签名兼容,函数指针、lambda、std::bind 结果或自定义函数对象都能装进去,以统一类型按值保存和复制。代价是一次可能的堆分配和一次间接调用。标准提案为 N1402。

基础用法

模板参数写签名 R(Args...);用 operator= 或构造函数放入任意兼容可调用对象,调用语法与普通函数相同。空 std::function 调用会抛 std::bad_function_call,用 operator bool() 判空。

#include <functional>
#include <iostream>
#include <typeinfo>

int add(int a, int b) { return a + b; }

int main() {
  std::function<int(int, int)> op = add;
  int offset = 10;
  op = [offset](int a, int b) { return a + b + offset; };

  auto copy = op;
  std::cout << op(1, 2) << ' ' << copy(1, 2) << ' '
            << (op.target_type() == typeid(int (*)(int, int)) ? "raw"
                                                              : "lambda")
            << '\n';

  std::cout << (op ? "set" : "empty") << ' ';
  op = nullptr;
  std::cout << (op ? "set" : "empty") << '\n';

  auto bind_add = std::bind(add, 5, std::placeholders::_1);
  std::cout << bind_add(7) << '\n';
}

输出 13 13 lambda、set empty 与 12。示例覆盖普通函数、捕获状态的 lambda、复制、target_type() 检查、置空判空,以及 std::bind 绑定部分参数;完整版本见 examples/cpp11/function.cpp。

进阶用法

std::function 的对象必须可复制。捕获了 std::unique_ptr 的 lambda 装不进去;需要传递时用 std::ref / std::cref 包一层引用包装,或改用 C++23 的 std::move_only_function。只读借用则可以用 absl::FunctionRef 或自写 function_ref,完全不拥有目标。

小对象缓冲决定是否分配:捕获总量装得下内部缓冲时移动构造不碰堆,超出才分配。拷贝 std::function 会复制被捕获对象;接口参数写成 const std::function<...>& 可以省掉这次复制,按值接收只在需要存储或转移时使用。

target_type() 返回目标的 type_info,target<T>() 取回原对象指针,需要恢复具体类型时才有用,日常回调路径不应依赖它。重载函数不能直接赋值,先用 static_cast 选定函数指针;泛型 lambda 也要用具体签名实例化后才能放入。

性能敏感的内层循环更适合模板参数或 function_ref:模板能在调用点内联,std::function 每次调用都要经过类型擦除后的虚函数式间接跳转。跨模块、跨线程队列、需要存储在容器里的回调则通常值得这次间接调用。

真实项目证据

项目 位置 用法 为什么典型
drogon lib/inc/drogon/HttpBinder.h#L206-L219 异步响应回调按右值引用接收后 std::move 交给下游 用户回调签名固定但状态任意,擦除后才能统一排队和跨层传递
spdlog include/spdlog/sinks/callback_sink.h#L15-L29 typedef 固化回调签名,sink 按值保存并在每条日志上调用 日志库不知道用户回调类型,std::function 让 sink 统一存储任意可调用对象
abseil absl/flags/reflection.cc#L183-L192 ForEachFlag 接收访问者并在两条遍历路径上调用 遍历接口不暴露注册表内部结构,访问者状态由 std::function 承担

三条证据均为 manual 级:作者复核,尚未引入第二名审核者。三处都在生产路径上,覆盖异步回调、可调用对象存储与遍历访问者三种形态。

陷阱与边界

空 std::function 调用会抛异常。 默认构造的 std::function 不含目标,直接调用抛 std::bad_function_call;调用前用 if (fn) 检查,或在接口处禁止传入空值。

可能有堆分配。 lambda 捕获的对象装得下 std::function 的内部小缓冲时不分配;捕获过多或过大就会走堆。热路径上需要关注分配成本,必要时改用模板参数或虚接口。

会复制被捕获的对象。 把带状态的 lambda 放入 std::function 会复制它的捕获;需要移动语义时用 std::move 构造 std::function,并要求被调用对象支持移动。

std::bind 不是首选。 C++11 的 std::bind 难以阅读且丢失参数类型信息;优先用 lambda 完成同样的绑定,行为和错误信息都更清楚。

类型擦除丢掉的是类型不是生命周期。 std::function 按值保存目标,目标引用的外部对象仍需自己保证存活;常见错误是放入了捕获 this 的 lambda,对象先于回调析构。

重载函数要显式转换。 std::function<int(int)> f = overload_set; 无法推导;先转成具体函数指针再放入。

不可复制的目标需要包装。 捕获 unique_ptr 的 lambda 无法直接存入;用 std::ref 共享外部对象,或等待 C++23 的 std::move_only_function。

库联动

  • <functional>:std::bind、占位符 _1/_2、std::ref / std::cref、std::mem_fn 配合 std::function 使用;C++17 的 std::invoke 提供统一的调用语法。
  • <algorithm>:谓词参数通常直接收 lambda 或模板,不需要 std::function;只有在运行期更换谓词时才值得擦除。
  • spdlog / drogon:日志 sink 用 std::function 存储用户回调,Web 框架用它承载异步响应处理。
  • abseil:absl::FunctionRef 提供不拥有的轻量引用,absl::AnyInvocable 支持一次性移动语义,是 std::function 的两种替代。
  • C++23 std::move_only_function:承载不可复制的可调用对象,比如捕获了 unique_ptr 的 lambda。

横向对比

方案 可携带状态 运行时开销 接口侵入性 主要限制
std::function 是 可能分配 + 间接调用 低,签名固定 类型擦除成本,空调用抛异常
函数指针 否 一次间接调用 低 无法捕获状态
虚函数接口 是 虚表间接调用 高,需为每个回调写子类 类型层次僵硬,复制语义要自己管
模板参数 是 可内联,通常最优 高,类型扩散到调用方 代码膨胀,无法放在统一容器里

跨标准演进:C++11 通过 N1402 引入 std::function,同时 std::bind 与 lambda 一并到位;C++17 起 std::function 可以从函数指针构造并支持 constexpr 的一部分用法;C++23 增加 std::move_only_function,用于承载不可复制的可调用对象。