可调用对象的绑定器和包装器
在 C++ 中,可调用对象的绑定器和包装器是用来简化或增强对函数(包括普通函数、成员函数、函数对象、Lambda 表达式等)的调用操作的工具。它们广泛用于泛型编程和 STL 算法中,以便将函数灵活地传递给算法或动态控制其行为。
1. 可调用对象的绑定器
绑定器的作用是将一个可调用对象的一部分参数提前绑定,从而生成一个新的可调用对象。
C++11 起引入了 std::bind
,用于创建一个可调用对象并绑定部分参数;在现代 C++(C++20)中,更推荐使用 Lambda 表达式 替代 std::bind
。
1.1 std::bind
std::bind
是一个函数模板,它可以接受一个可调用对象(如普通函数、成员函数、函数对象等)及部分参数,并返回一个新的可调用对象,该对象可以像函数一样调用。
语法
1 |
|
callable
:要绑定的可调用对象。arg1, arg2, ..., argN
:可调用对象的参数,其中可以使用占位符_1
,_2
, … 来延迟绑定。
占位符 _1
, _2
, … 来自头文件 <functional>
,表示绑定时保留的参数位置。
例子 1:绑定普通函数
1 |
|
解释:
std::bind(add, 5, _1)
创建了一个新函数对象add_five
,它将add
的第一个参数固定为5
,第二个参数保留动态输入。- 调用
add_five(10)
等价于调用add(5, 10)
。
例子 2:绑定成员函数
1 |
|
解释:
- 成员函数绑定时,需要提供对象指针(如
&obj
)作为隐式的this
参数。 std::bind(&MyClass::display, &obj, _1)
将成员函数的调用与对象绑定。
1.2 替代 std::bind 的 Lambda 表达式
在现代 C++ 中,Lambda 表达式更简洁,功能更强,因此可以用 Lambda 替代 std::bind
。
以上绑定普通函数的例子,用 Lambda 表达式可以写成:
1 | auto add_five = [](int b) { return add(5, b); }; |
绑定成员函数的例子,用 Lambda 表达式可以写成:
1 | auto bound_fn = [&obj](int x) { obj.display(x); }; |
2. 可调用对象的包装器
包装器的作用是将一个可调用对象封装起来,允许在运行时管理其调用行为。
2.1 std::function
std::function
是一个通用的函数包装器,用来存储和调用任何符合特定签名的可调用对象。
它可以存储:
- 普通函数
- 函数指针
- 成员函数指针
- Lambda 表达式
- 仿函数
语法
1 |
|
R
:函数的返回类型。Args...
:函数的参数类型列表。
例子 1:包装普通函数
1 |
|
例子 2:包装 Lambda 表达式
1 |
|
例子 3:包装仿函数
1 |
|
2.2 std::function 与 std::bind 配合使用
std::function
可以与 std::bind
结合使用,将部分参数绑定后存储。
1 |
|
2.3 std::function 的灵活性
std::function
允许运行时动态更改存储的可调用对象。
1 |
|
3. 区别与应用场景
特性 | std::bind | std::function |
---|---|---|
作用 | 用于绑定函数的部分参数,生成新的可调用对象 | 用于包装函数,统一管理所有可调用对象 |
参数绑定 | 支持占位符 _1 , _2 等 |
不支持参数绑定 |
运行时动态性 | 固定绑定时传入的函数和参数 | 可以运行时动态更改存储的可调用对象 |
替代方案 | Lambda 表达式可以完全替代 | 无明显替代方案(功能独特) |
总结
- **
std::bind
**:用于将函数的部分参数提前绑定生成一个新的可调用对象,但在现代 C++ 中,通常使用更直观的 Lambda 表达式 代替。 - **
std::function
**:是一个灵活的函数包装器,可以统一存储和调用各种可调用对象(普通函数、Lambda、仿函数等),适用于需要动态改变函数行为的场景。
结合 std::bind
和 std::function
(如 ThreadFunc
),我们可以更灵活地创建回调函数或者线程入口函数。std::bind
允许你将某些函数的参数预先绑定,生成一个新的可调用对象。这个新的可调用对象可以通过 std::function
传递,作为回调或者线程的入口函数。
拓展
结合 std::bind
和 ThreadFunc
的用法
假设你需要通过 std::bind
绑定函数的某些参数,然后将其传递给一个线程或其他需要可调用对象的地方。使用 std::function
和 std::bind
可以非常方便地处理这种情况。
示例:使用 std::bind
和 ThreadFunc
传递绑定参数
在这个例子中,我们将创建一个 ThreadFunc
类型的对象,使用 std::bind
绑定一个函数的部分参数,然后将绑定后的函数作为线程的入口函数。
1 |
|
解释:
ThreadFunc
类型别名:using ThreadFunc = std::function<void()>;
定义了一个新的类型ThreadFunc
,表示一个无参数且返回void
的可调用对象。std::bind
的使用:std::bind(print_sum, 10, 20)
创建了一个新的函数对象,它绑定了print_sum
函数的两个参数a
和b
,分别为 10 和 20。返回的是一个新的可调用对象,它没有参数,可以通过ThreadFunc
来表示。- 传递给线程:
std::thread t(func);
创建了一个新的线程t
,并将func
(一个经过std::bind
绑定的函数对象)作为线程的入口函数。 - 等待线程执行:
t.join();
等待线程执行完毕。
示例:结合 std::bind
和 lambda
你也可以将 std::bind
与 lambda 表达式结合,动态地传递参数。在这种情况下,lambda 表达式可以充当回调函数的一部分,std::bind
则允许你预先绑定一些参数。
1 |
|
总结:
std::bind
和ThreadFunc
结合使用时,std::bind
可以为函数创建一个新的可调用对象,绑定部分参数,生成一个无参数的函数对象。这个对象可以通过ThreadFunc
类型来表示。- 灵活性:
std::bind
使得我们可以预先绑定一些参数,创建更灵活的回调函数或线程入口函数。结合std::function
类型别名后,代码会更加简洁和清晰。 - 代码简化:在多线程和回调编程中,使用
std::bind
和ThreadFunc
可以极大地简化参数传递和回调逻辑,尤其是当你需要传递复杂的函数或绑定多个参数时。
这种方式常用于多线程编程和事件驱动编程中,使得代码更加模块化、灵活。