STL 标准库里提供了一些函数包装与绑定模板,可以把"可调用对象"统一包装成一种类型,方便在函数之间传来传去、当作回调。
一、std::function:可调用对象包装器
头文件:#include <functional>
std::function<R(Args...)> 是可调用对象包装器。一句话:可以把普通函数、lambda、函数对象、成员函数全部包装成同一个类型,统一调用。
模板参数 R(Args...) 中:
R:返回值类型
Args...:参数列表(可变参数包)
1.1 能包装哪些东西
| 可调用类型 |
是否能包装进 function |
| 普通全局函数 |
✅ |
| Lambda 表达式 |
✅(带不带捕获都行) |
重载了 operator() 的函数对象(仿函数) |
✅ |
| 类成员函数 / const 成员函数 |
✅(需要额外处理 this) |
1.2 基础示例:包装普通函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <functional> using namespace std;
int foo(int x) { return x * 2; }
int main() { function<int(int)> f;
f = foo; cout << f(10);
f = [](int x) { return x + 5; }; cout << f(10); return 0; }
|
同一个变量 f,可以先后保存不同的可调用对象,类型不变——这是它最大的价值。
1.3 包装仿函数(函数对象)
1 2 3 4 5 6 7 8 9 10 11
| struct Functor { void operator()(int a) { cout << a << endl; } };
int main() { function<void(int)> f = Functor(); f(99); return 0; }
|
1.4 包装成员函数(重点)
成员函数自带隐藏的 this 参数,三种方案:
方案 A:直接把 this 作为第一个参数写到 function 签名
1 2 3 4 5 6 7 8 9 10 11
| struct Test { int sum(int a, int b) { return a + b; } };
int main() { function<int(Test*, int, int)> f = &Test::sum; Test t; cout << f(&t, 1, 2); return 0; }
|
方案 B:bind 绑定 this 和占位符(提前绑定对象)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <functional> #include <placeholders> using namespace std::placeholders;
struct Test { int sum(int a, int b) { return a + b; } };
int main() { Test t; function<int(int, int)> f = bind(&Test::sum, &t, _1, _2); cout << f(3, 4); return 0; }
|
方案 C:lambda 捕获(最简单,竞赛首选)
1 2 3 4 5 6 7 8 9 10
| struct Test { int sum(int a, int b) { return a + b; } };
int main() { Test t; function<int(int, int)> f = [&t](int a, int b) { return t.sum(a, b); }; cout << f(5, 6); return 0; }
|
1.5 判断 function 是否为空
没绑定任何可调用对象时,直接调用会抛异常 std::bad_function_call:
1 2 3 4 5 6
| function<void()> f; if (f) { f(); } else { cout << "空,不能调用\n"; }
|
1.6 std::function 的底层原理(了解)
std::function 内部用类型擦除(type erasure):
- 不管你传入的是 lambda、函数指针、仿函数,统统包装成同一个
function 类型;
- 代价:一点点运行时开销(虚函数调用 + 堆分配)。
对比:原生函数指针没有这个开销,但不能存带捕获的 lambda。
不带捕获的 lambda 可以隐式转换成函数指针;带捕获的 lambda 不能转函数指针,但可以放入 std::function。
1.7 常见坑
- 调用空的
function 会抛异常 bad_function_call,调用前最好判空。
- 绑定成员函数时,注意对象生命周期——对象销毁后
function 里保存的 this 就成了野指针,未定义行为。
- 类型严格匹配:
function<int(int)> 只能包装「参数 int、返回 int」的可调用对象,类型不匹配编译报错。
- 有少量性能开销,极致卡常场景尽量避免频繁创建。
1.8 使用场景
- 回调函数(最常用)
- 把不同类型的可调用对象存入容器:
vector<function<void()>> tasks;
- 实现策略模式,运行时切换执行函数
1 2 3 4 5
| vector<function<void()>> v; v.push_back([]() { cout << "task1\n"; }); v.push_back([]() { cout << "task2\n"; }); for (auto& func : v) func();
|
二、std::bind:参数绑定 + 顺序调整
头文件:<functional>
std::bind(可调用对象, 参数...) 把可调用对象和参数"绑"在一起,返回一个新的可调用对象。未提供的参数用 std::placeholders::_1、_2... 占位,调用时按顺序填入。
2.1 基础示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <iostream> #include <functional> using namespace std; using namespace std::placeholders;
int add(int a, int b, int c) { return a + b + c; }
int main() { auto f = bind(add, 10, _1, _2); cout << f(1, 2); return 0; }
|
2.2 调整参数顺序
1 2 3 4 5 6 7 8 9 10
| int sub(int a, int b) { return a - b; }
int main() { auto f = bind(sub, _2, _1); cout << f(10, 3); return 0; }
|
2.3 绑定成员函数(结合 this)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct Calculator { int base; int add(int a, int b) { return base + a + b; } };
int main() { Calculator c{100}; auto f = bind(&Calculator::add, &c, _1, _2); cout << f(1, 2); return 0; }
|
2.4 bind 的坑与替代
- C++11 之前:
bind 是函数式编程的核心工具;
- C++11 之后:多数场景下 lambda 完全可以替代
bind,而且更直观——尤其是不需要"重排参数顺序"时,直接写 lambda 更好。
| 场景 |
推荐 |
| 固定某些参数 |
lambda 捕获:[x](y) { return foo(x, y); } |
| 调整参数顺序 |
bind(或者直接写 lambda 显式调换) |
| 绑定成员函数 + 提前绑对象 |
bind 或 lambda 捕获 |
C++17 起建议:能用 lambda 就用 lambda,bind 仅在需要"重排参数顺序"或"把成员函数包装成 function"时才用它。
三、极简速记
| 工具 |
一句话 |
std::function |
把所有可调用对象统一成一种类型 |
std::bind |
绑定部分参数 / 重排参数顺序 |
std::placeholders::_1 |
占位符,第 1 个参数 |
lambda 捕获 [x] |
替代 bind 最常见的写法 |
四、小结
std::function:可调用对象包装器,统一接口;
std::bind:把参数绑定 / 重排,但 C++11 之后多被 lambda 替代;
- 成员函数包装:优先 lambda 捕获,
bind 次之;
- 实际工程里大多数场景
auto + lambda + function 三件套就够用。