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() {
// 包装:接收 int,返回 int
function<int(int)> f;

f = foo; // 绑定普通函数
cout << f(10); // 20

f = [](int x) { return x + 5; }; // 换成 lambda
cout << f(10); // 15
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() {
// 签名第一个参数是 Test*(this 指针)
function<int(Test*, int, int)> f = &Test::sum;
Test t;
cout << f(&t, 1, 2); // 3
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); // 7
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); // 11
return 0;
}

1.5 判断 function 是否为空

没绑定任何可调用对象时,直接调用会抛异常 std::bad_function_call:

1
2
3
4
5
6
function<void()> f;
if (f) { // 重载了 operator bool:有目标返回 true,空则 false
f();
} else {
cout << "空,不能调用\n";
}

1.6 std::function 的底层原理(了解)

std::function 内部用类型擦除(type erasure):

  • 不管你传入的是 lambda、函数指针、仿函数,统统包装成同一个 function 类型;
  • 代价:一点点运行时开销(虚函数调用 + 堆分配)。

对比:原生函数指针没有这个开销,但不能存带捕获的 lambda。
不带捕获的 lambda 可以隐式转换成函数指针;带捕获的 lambda 不能转函数指针,但可以放入 std::function。

1.7 常见坑

  1. 调用空的 function 会抛异常 bad_function_call,调用前最好判空。
  2. 绑定成员函数时,注意对象生命周期——对象销毁后 function 里保存的 this 就成了野指针,未定义行为。
  3. 类型严格匹配:function<int(int)> 只能包装「参数 int、返回 int」的可调用对象,类型不匹配编译报错。
  4. 有少量性能开销,极致卡常场景尽量避免频繁创建。

1.8 使用场景

  • 回调函数(最常用)
  • 把不同类型的可调用对象存入容器:vector<function<void()>> tasks;
  • 实现策略模式,运行时切换执行函数
1
2
3
4
5
// vector 存放多个不同可调用对象示例
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() {
// 把第一个参数固定为 10
auto f = bind(add, 10, _1, _2);
cout << f(1, 2); // 10 + 1 + 2 = 13
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() {
// 让 b 变成"第一个参数"
auto f = bind(sub, _2, _1);
cout << f(10, 3); // sub(3, 10) = -7
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};
// 把 c 当作 this,base 已固定(隐式),剩下两个参数用占位符
auto f = bind(&Calculator::add, &c, _1, _2);
cout << f(1, 2); // 100 + 1 + 2 = 103
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 三件套就够用。