条款34:优先选用 lambda 式,而非 std bind
在 C++11 中,lambda 表达式几乎总是比 std::bind 更好的选择;到了 C++14,std::bind 基本上已无用武之地。std::bind 是一个源自 C++98 时代函数式编程思想的工具,虽然在 C++11 标准库中被正式引入,但其设计与现代 C++ 的 lambda 表达式相比,在多个方面都相形见绌。
std::bind
std::bind 是 C++11 在
简而言之,std::bind 的核心作用是延迟调用和参数定制。
std::bind 的基本语法如下:
1 | auto new_callable = std::bind(callable_object, arg1, arg2, ...); |
- callable_object: 任何可以被调用的对象,例如普通函数指针, 类的成员函数指针, Lambda 表达式, 其他函数对象(如 std::function)等
- arg1, arg2, …: 要传递给 callable_object 的参数列表。这些参数可以是具体的值(如 10, “hello”, 3.14), 这些参数会被复制或移动并存储在生成的新函数对象中; 也可以是占位符 (Placeholders): 如 _1, _2, _3, …。它们定义在命名空间 std::placeholders 中。占位符表示新生成的可调用对象的参数位置。例如,_1 表示新对象的第一个参数,_2 表示第二个参数,以此类推(注意:使用占位符时,通常需要 using namespace std::placeholders; 或显式指定 std::placeholders::_1)
主要用途与代码示例
下面我们通过几个核心场景来理解 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
26
27
28
29
30
31
32
33
34
// 原始函数,接受三个参数
void print_info(const std::string& name, int age, const std::string& city) {
std::cout << name << " is " << age << " years old and lives in " << city << "." << std::endl;
}
int main() {
// 使用占位符命名空间
using namespace std::placeholders;
// 1. 绑定部分参数
// 将 print_info 的第一个和第三个参数固定为 "Alice" 和 "New York"
// _1 是一个占位符,代表新函数 call_alice 的第一个参数
// 第二个参数被设置为占位符 _1,这意味着 call_alice 的第一个参数将会被传递到 print_info 的第二个位置。
auto call_alice = std::bind(print_info, "Alice", _1, "New York");
// 调用新生成的函数对象,只需要提供年龄
std::cout << "--- Calling with partial binding ---" << std::endl;
call_alice(30); // 输出: Alice is 30 years old and lives in New York.
call_alice(25); // 输出: Alice is 25 years old and lives in New York.
// 2. 绑定所有参数
// 将所有参数都固定下来
auto call_bob = std::bind(print_info, "Bob", 42, "London");
// 调用时不再需要任何参数
std::cout << "\n--- Calling with full binding ---" << std::endl;
call_bob(); // 输出: Bob is 42 years old and lives in London.
return 0;
} - 调整参数顺序: 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
double subtract(double a, double b) {
return a - b;
}
int main() {
using namespace std::placeholders;
// 原始调用
std::cout << "subtract(10, 3) = " << subtract(10, 3) << std::endl; // 输出: 7
// 使用 bind 交换参数顺序
// _1 对应新函数的第一个参数,_2 对应第二个
// bind(subtract, _2, _1) 的意思是:
// 调用时,将新函数的第二个参数传给 subtract 的第一个参数
// 将新函数的第一个参数传给 subtract 的第二个参数
auto reversed_subtract = std::bind(subtract, _2, _1);
std::cout << "reversed_subtract(10, 3) = " << reversed_subtract(10, 3) << std::endl; // 输出: -7
// 上述调用等效于 subtract(3, 10)
return 0;
} - 绑定类的成员函数: 这是 std::bind
一个非常重要的应用场景,尤其是在回调函数中。绑定成员函数时,必须提供一个类的实例(或指针、引用)作为
std::bind 的第一个参数(在函数参数之后)。也就是说,
绑定成员函数时,第一个参数必须是成员函数指针,第二个参数必须是对象实例(或指针)
> &greeter_instance:这是调用该成员函数的对象实例的指针。this 指针被隐式地绑定到了 greeter_instance。你也可以直接传递 greeter_instance,此时会拷贝一份对象。
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
26
27
28
29
30
class Greeter {
public:
void say_hello(const std::string& name) {
std::cout << "Hello, " << name << "!" << std::endl;
}
};
int main() {
using namespace std::placeholders;
Greeter greeter_instance;
// 绑定成员函数
// 第一个参数是成员函数指针:&Greeter::say_hello
// 第二个参数是对象实例的地址:&greeter_instance
// 第三个参数 _1 是占位符,对应 say_hello 的 name 参数
auto greet_func = std::bind(&Greeter::say_hello, &greeter_instance, _1);
greet_func("World"); // 输出: Hello, World!
greet_func("C++"); // 输出: Hello, C++!
// 如果传递对象实例本身,会发生拷贝
auto greet_func_copy = std::bind(&Greeter::say_hello, greeter_instance, _1);
greet_func_copy("Copied"); // 输出: Hello, Copied!
return 0;
}
std::bind 与 Lambda 表达式的对比
在现代 C++ (C++14 及以后) 中,Lambda 表达式通常是比 std::bind 更好的选择。因为 Lambda 通常更具可读性、更灵活,并且可能产生更高效的代码。让我们用 Lambda 重写上面的第一个例子:
1 |
|