引言
在前面 STL 系列中,我们学习了仿函数和 Lambda。今天要讲的std::bind是另一种处理可调用对象的方式。它的核心作用很简单:把一个函数(或仿函数、Lambda)的部分参数提前绑定好,生成一个新的可调用对象。
比如你有一个func(int a, int b),你可以用bind把a绑定为10,生成一个新的函数newFunc(int b),调用newFunc(5)就等价于func(10, 5)。
在现代 C++ 中,Lambda 已经能覆盖bind的大部分用途,但bind在某些场景下仍然有用:配合占位符灵活重排参数顺序、与老式 STL 算法搭配等。
第一部分:基本语法
一、头文件与占位符
#include <iostream> #include <functional> // bind 所在头文件 using namespace std; using namespace std::placeholders; // _1, _2, _3 等占位符占位符:_1表示新函数的第 1 个参数,_2表示第 2 个参数,以此类推。std::bind最多支持 29 个占位符(_1到_29)。
二、最简单的例子
#include <iostream> #include <functional> using namespace std; using namespace std::placeholders; // 原始函数:三个参数 void show(int a, int b, int c) { cout << a << ", " << b << ", " << c << endl; } int main() { // bind 所有参数 auto f1 = bind(show, 10, 20, 30); f1(); // 10, 20, 30 // 只绑定部分参数,其余用占位符 auto f2 = bind(show, 100, _1, _2); f2(200, 300); // 100, 200, 300 // 调换参数顺序 auto f3 = bind(show, _3, _2, _1); f3(1, 2, 3); // 3, 2, 1(顺序被反转了!) return 0; }第二部分:常见用法
一、绑定普通函数
int add(int a, int b) { return a + b; } int main() { // 绑定第一个参数为 10 auto add10 = bind(add, 10, _1); cout << add10(5) << endl; // 15 cout << add10(20) << endl; // 30 // 绑定两个参数 auto add3and5 = bind(add, 3, 5); cout << add3and5() << endl; // 8 return 0; }二、绑定成员函数
绑定成员函数时,第一个绑定的参数必须是对象的地址(或对象本身)。
#include <iostream> #include <functional> using namespace std; using namespace std::placeholders; class Calculator { public: int add(int a, int b) { return a + b; } }; int main() { Calculator calc; // 绑定成员函数:第一个参数传对象地址 auto f = bind(&Calculator::add, &calc, _1, _2); cout << f(3, 5) << endl; // 8 // 也可以传对象本身(会拷贝一份) auto g = bind(&Calculator::add, calc, _1, _2); cout << g(10, 20) << endl; // 30 return 0; }注意:&Calculator::add前面的&不能省略。bind的第一个参数是成员函数指针,需要显式取地址。
三、绑定仿函数
struct Multiplier { int operator()(int a, int b) const { return a * b; } }; int main() { Multiplier mul; auto times5 = bind(mul, _1, 5); cout << times5(3) << endl; // 15 return 0; }四、绑定 Lambda
int main() { auto lambda = [](int a, int b, int c) { return a + b + c; }; // 绑定第一个参数为 100 auto f = bind(lambda, 100, _1, _2); cout << f(20, 3) << endl; // 123 return 0; }第三部分:占位符的灵活使用
一、重排参数顺序
void print(int x, int y, int z) { cout << x << " " << y << " " << z << endl; } int main() { // 反转参数顺序 auto f = bind(print, _3, _2, _1); f(1, 2, 3); // 3 2 1 // 重复使用某个占位符(同一个值传两次) auto g = bind(print, _1, _1, _2); g(10, 20); // 10 10 20 return 0; }二、占位符传递引用
当原始函数接受引用参数时,需要用std::ref或std::cref传递引用,否则bind默认是值传递(会拷贝一份)。
#include <functional> void increment(int& x) { x++; } int main() { int n = 10; // ❌ 错误:bind 默认值传递,n 被拷贝,原变量不会被修改 // auto f = bind(increment, n); // ✅ 正确:用 ref 传递引用 auto f = bind(increment, ref(n)); f(); cout << n << endl; // 11 // cref 传递 const 引用 auto g = bind([](const int& x) { cout << x; }, cref(n)); g(); // 11 return 0; }第四部分:配合 STL 算法
一、配合 find_if
#include <algorithm> #include <vector> using namespace std; bool isGreaterThan(int value, int threshold) { return value > threshold; } int main() { vector<int> v = {1, 5, 3, 8, 2}; // 用 bind 把 threshold 绑定为 4 auto it = find_if(v.begin(), v.end(), bind(isGreaterThan, _1, 4)); if (it != v.end()) cout << *it << endl; // 5(第一个大于4的) return 0; }二、配合 transform
#include <algorithm> #include <vector> using namespace std; int multiply(int a, int b) { return a * b; } int main() { vector<int> v = {1, 2, 3, 4, 5}; vector<int> result(v.size()); // 每个元素乘以 10 transform(v.begin(), v.end(), result.begin(), bind(multiply, _1, 10)); // result: 10 20 30 40 50 return 0; }第五部分:bind vs Lambda
| 对比项 | std::bind | Lambda |
|---|---|---|
| 可读性 | 较差(占位符不直观) | 好(参数名明确) |
| 性能 | 可能引入额外开销 | 通常更优(可内联) |
| 灵活性 | 方便重排参数顺序 | 需要手动写逻辑 |
| 学习成本 | 需要理解占位符 | 语法直观 |
| 现代推荐 | 仅特殊场景 | 首选 |
// 同样的功能,两种写法 // bind 写法 auto f1 = bind(multiply, _1, 10); transform(v.begin(), v.end(), result.begin(), f1); // Lambda 写法(更直观) transform(v.begin(), v.end(), result.begin(), [](int x) { return x * 10; });总结
一、核心要点
| 要点 | 内容 |
|---|---|
| 头文件 | <functional> |
| 占位符 | _1, _2, _3...,命名空间std::placeholders |
| 绑定普通函数 | bind(func, 实参或占位符...) |
| 绑定成员函数 | bind(&Class::method, &obj, 实参或占位符...) |
| 传递引用 | ref(obj)或cref(obj) |
二、一句话记忆
std::bind把函数的部分参数提前绑定(固定值或占位符),生成参数更少的新可调用对象。成员函数需传对象地址,引用参数需用ref。现代 C++ 中大部分场景用 Lambda 替代,只在重排参数顺序等特殊场景使用。