在 C++ 中,可调用对象的绑定器和包装器是用来简化或增强对函数(包括普通函数、成员函数、函数对象、Lambda 表达式等)的调用操作的工具。它们广泛用于泛型编程和 STL 算法中,以便将函数灵活地传递给算法或动态控制其行为。


1. 可调用对象的绑定器

绑定器的作用是将一个可调用对象的一部分参数提前绑定,从而生成一个新的可调用对象。

C++11 起引入了 std::bind,用于创建一个可调用对象并绑定部分参数;在现代 C++(C++20)中,更推荐使用 Lambda 表达式 替代 std::bind

1.1 std::bind

std::bind 是一个函数模板,它可以接受一个可调用对象(如普通函数、成员函数、函数对象等)及部分参数,并返回一个新的可调用对象,该对象可以像函数一样调用。

语法

1
2
3
#include <functional> // std::bind 所在的头文件

std::bind(callable, arg1, arg2, ..., argN);
  • callable:要绑定的可调用对象。
  • arg1, arg2, ..., argN:可调用对象的参数,其中可以使用占位符 _1, _2, … 来延迟绑定。

占位符 _1, _2, … 来自头文件 <functional>,表示绑定时保留的参数位置。


例子 1:绑定普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <functional> // std::bind, std::placeholders
using namespace std;

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

int main() {
using namespace std::placeholders; // 使用占位符 _1, _2

auto add_five = std::bind(add, 5, _1); // 绑定第一个参数为 5
cout << add_five(10) << endl; // 输出 15(5 + 10)

return 0;
}

解释:

  • std::bind(add, 5, _1) 创建了一个新函数对象 add_five,它将 add 的第一个参数固定为 5,第二个参数保留动态输入。
  • 调用 add_five(10) 等价于调用 add(5, 10)

例子 2:绑定成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <functional>
using namespace std;

class MyClass {
public:
void display(int x) const {
cout << "Value: " << x << endl;
}
};

int main() {
using namespace std::placeholders;

MyClass obj;
auto bound_fn = std::bind(&MyClass::display, &obj, _1); // 绑定成员函数
bound_fn(42); // 输出 Value: 42

return 0;
}

解释:

  • 成员函数绑定时,需要提供对象指针(如 &obj)作为隐式的 this 参数。
  • std::bind(&MyClass::display, &obj, _1) 将成员函数的调用与对象绑定。

1.2 替代 std::bind 的 Lambda 表达式

在现代 C++ 中,Lambda 表达式更简洁,功能更强,因此可以用 Lambda 替代 std::bind

以上绑定普通函数的例子,用 Lambda 表达式可以写成:

1
2
auto add_five = [](int b) { return add(5, b); };
cout << add_five(10) << endl; // 输出 15

绑定成员函数的例子,用 Lambda 表达式可以写成:

1
2
auto bound_fn = [&obj](int x) { obj.display(x); };
bound_fn(42); // 输出 Value: 42

2. 可调用对象的包装器

包装器的作用是将一个可调用对象封装起来,允许在运行时管理其调用行为

2.1 std::function

std::function 是一个通用的函数包装器,用来存储和调用任何符合特定签名的可调用对象。
它可以存储:

  • 普通函数
  • 函数指针
  • 成员函数指针
  • Lambda 表达式
  • 仿函数

语法

1
2
3
#include <functional> // std::function 所在的头文件

std::function<R(Args...)>
  • R:函数的返回类型。
  • Args...:函数的参数类型列表。

例子 1:包装普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <functional> // std::function
using namespace std;

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

int main() {
std::function<int(int, int)> func = add; // 包装普通函数
cout << func(3, 7) << endl; // 输出 10
return 0;
}

例子 2:包装 Lambda 表达式

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <functional>
using namespace std;

int main() {
std::function<int(int, int)> func = [](int a, int b) { return a * b; }; // 包装 Lambda
cout << func(4, 5) << endl; // 输出 20
return 0;
}

例子 3:包装仿函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <functional>
using namespace std;

struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};

int main() {
std::function<int(int, int)> func = Multiply(); // 包装仿函数
cout << func(6, 7) << endl; // 输出 42
return 0;
}

2.2 std::function 与 std::bind 配合使用

std::function 可以与 std::bind 结合使用,将部分参数绑定后存储。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <functional>
using namespace std;

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

int main() {
using namespace std::placeholders;

std::function<int(int)> func = std::bind(add, 5, _1); // 绑定第一个参数为 5
cout << func(10) << endl; // 输出 15

return 0;
}

2.3 std::function 的灵活性

std::function 允许运行时动态更改存储的可调用对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <functional>
using namespace std;

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

int subtract(int a, int b) {
return a - b;
}

int main() {
std::function<int(int, int)> func;

func = add; // 动态存储 add
cout << func(10, 5) << endl; // 输出 15

func = subtract; // 动态存储 subtract
cout << func(10, 5) << endl; // 输出 5

return 0;
}

3. 区别与应用场景

特性 std::bind std::function
作用 用于绑定函数的部分参数,生成新的可调用对象 用于包装函数,统一管理所有可调用对象
参数绑定 支持占位符 _1, _2 不支持参数绑定
运行时动态性 固定绑定时传入的函数和参数 可以运行时动态更改存储的可调用对象
替代方案 Lambda 表达式可以完全替代 无明显替代方案(功能独特)

总结

  • **std::bind**:用于将函数的部分参数提前绑定生成一个新的可调用对象,但在现代 C++ 中,通常使用更直观的 Lambda 表达式 代替。
  • **std::function**:是一个灵活的函数包装器,可以统一存储和调用各种可调用对象(普通函数、Lambda、仿函数等),适用于需要动态改变函数行为的场景。

结合 std::bindstd::function(如 ThreadFunc),我们可以更灵活地创建回调函数或者线程入口函数。std::bind 允许你将某些函数的参数预先绑定,生成一个新的可调用对象。这个新的可调用对象可以通过 std::function 传递,作为回调或者线程的入口函数。

拓展

结合 std::bindThreadFunc 的用法

假设你需要通过 std::bind 绑定函数的某些参数,然后将其传递给一个线程或其他需要可调用对象的地方。使用 std::functionstd::bind 可以非常方便地处理这种情况。

示例:使用 std::bindThreadFunc 传递绑定参数

在这个例子中,我们将创建一个 ThreadFunc 类型的对象,使用 std::bind 绑定一个函数的部分参数,然后将绑定后的函数作为线程的入口函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <thread>
#include <functional>

// 定义线程函数类型别名
using ThreadFunc = std::function<void()>;

// 一个示例线程函数,接受两个参数
void print_sum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}

int main() {
// 使用 std::bind 绑定 print_sum 的两个参数
// 绑定参数 a = 10,b = 20
ThreadFunc func = std::bind(print_sum, 10, 20);

// 创建一个线程并传递绑定后的函数作为入口函数
std::thread t(func);
t.join(); // 等待线程执行完毕

return 0;
}

解释:

  1. ThreadFunc 类型别名using ThreadFunc = std::function<void()>; 定义了一个新的类型 ThreadFunc,表示一个无参数且返回 void 的可调用对象。
  2. std::bind 的使用std::bind(print_sum, 10, 20) 创建了一个新的函数对象,它绑定了 print_sum 函数的两个参数 ab,分别为 10 和 20。返回的是一个新的可调用对象,它没有参数,可以通过 ThreadFunc 来表示。
  3. 传递给线程std::thread t(func); 创建了一个新的线程 t,并将 func(一个经过 std::bind 绑定的函数对象)作为线程的入口函数。
  4. 等待线程执行t.join(); 等待线程执行完毕。

示例:结合 std::bind 和 lambda

你也可以将 std::bind 与 lambda 表达式结合,动态地传递参数。在这种情况下,lambda 表达式可以充当回调函数的一部分,std::bind 则允许你预先绑定一些参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <thread>
#include <functional>

// 定义线程函数类型别名
using ThreadFunc = std::function<void()>;

// 一个示例线程函数,接受两个参数
void print_sum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}

int main() {
// 使用 std::bind 绑定一个函数,并结合 lambda 来处理参数
int x = 10, y = 20;
ThreadFunc func = std::bind([](int a, int b) {
print_sum(a, b); // 使用 lambda 来调用 print_sum
}, x, y);

// 创建一个线程并传递绑定后的函数作为入口函数
std::thread t(func);
t.join(); // 等待线程执行完毕

return 0;
}

总结:

  • std::bindThreadFunc 结合使用时,std::bind 可以为函数创建一个新的可调用对象,绑定部分参数,生成一个无参数的函数对象。这个对象可以通过 ThreadFunc 类型来表示。
  • 灵活性std::bind 使得我们可以预先绑定一些参数,创建更灵活的回调函数或线程入口函数。结合 std::function 类型别名后,代码会更加简洁和清晰。
  • 代码简化:在多线程和回调编程中,使用 std::bindThreadFunc 可以极大地简化参数传递和回调逻辑,尤其是当你需要传递复杂的函数或绑定多个参数时。

这种方式常用于多线程编程和事件驱动编程中,使得代码更加模块化、灵活。