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,用于承载不可复制的可调用对象。