通俗的来讲function是函数包装器,而bind绑定器的作用是将二元或者多元的仿函数降低维度。
C++11 引入的 std::function 和 std::bind 是函数式编程的核心组件,彻底改变了 C++ 中可调用对象的处理方式。它们解决了传统 C++ 中可调用对象类型不统一、参数无法灵活适配的问题,是实现回调、策略模式、延迟调用的基础。
一、核心概念
std::function:可调用对象的类型统一器 。它是一个模板类,能够包装任何可调用对象(普通函数、函数指针、lambda、仿函数、成员函数指针),并将其转换为具有统一签名的类型。std::bind:参数适配器 。它是一个函数模板,能够将一个可调用对象与部分参数绑定,生成一个新的可调用对象。支持固定参数值 、调整参数顺序 、延迟调用等功能。
二、std::function 详解
2.1 基本语法
cpp
#include <functional>
// 模板参数格式:返回值类型(参数类型列表)
std::function<返回值(参数1类型, 参数2类型, ...)> func;
2.2 包装所有可调用对象
std::function 最大的价值在于类型擦除 :它能将不同类型但签名相同的可调用对象,统一为同一个 std::function 类型。
cpp
#include <iostream>
#include <functional>
// 1. 普通函数
int add(int a, int b) { return a + b; }
// 2. 仿函数(函数对象)
struct Sub {
int operator()(int a, int b) const { return a - b; }
};
// 3. 类的普通成员函数
class Calculator {
public:
int mul(int a, int b) const { return a * b; }
static int div(int a, int b) { return a / b; }
};
int main() {
// 包装普通函数
std::function<int(int, int)> func1 = add;
std::cout << func1(2, 3) << std::endl; // 输出5
// 包装仿函数
std::function<int(int, int)> func2 = Sub();
std::cout << func2(5, 2) << std::endl; // 输出3
// 包装lambda表达式
std::function<int(int, int)> func3 = [](int a, int b) { return a % b; };
std::cout << func3(7, 3) << std::endl; // 输出1
// 包装静态成员函数(无需绑定对象)
std::function<int(int, int)> func4 = Calculator::div;
std::cout << func4(6, 2) << std::endl; // 输出3
// 包装普通成员函数(必须绑定对象实例,见下文bind部分)
Calculator calc;
std::function<int(int, int)> func5 = std::bind(&Calculator::mul, &calc, std::placeholders::_1, std::placeholders::_2);
std::cout << func5(3, 4) << std::endl; // 输出12
return 0;
}
2.3 核心特性
-
空值检查 :
std::function可以为空,调用空的std::function会抛出std::bad_function_call异常。cppstd::function<void()> empty_func; if (empty_func) { empty_func(); // 不会执行 } -
赋值与拷贝:支持拷贝构造、移动构造和赋值操作,可作为函数参数和返回值传递。
-
容器存储 :由于类型统一,可将多个不同类型的可调用对象存入同一个容器。
cppstd::vector<std::function<int(int, int)>> ops; ops.push_back(add); ops.push_back(Sub()); ops.push_back([](int a, int b) { return a * b; });
三、std::bind 详解
3.1 基本语法
cpp
#include <functional>
// 绑定可调用对象和参数
auto new_func = std::bind(可调用对象, 参数1, 参数2, ...);
- 参数 :可以是固定值 ,也可以是占位符 (
std::placeholders::_1,_2, ...,_N)。 - 占位符 :代表新可调用对象的第 N 个参数。例如
_1表示新函数的第一个参数,_2表示第二个参数。 - 返回值 :一个未指定类型的可调用对象,通常用
auto接收,或用std::function包装。
3.2 核心用法
用法 1:绑定固定参数(柯里化)
将原函数的部分参数固定为常量,生成一个参数更少的新函数。
cpp
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
// 绑定第一个参数为10,生成一个只需要1个参数的新函数
auto add10 = std::bind(add, 10, std::placeholders::_1);
std::cout << add10(5) << std::endl; // 等价于 add(10,5) → 15
std::cout << add10(20) << std::endl; // 等价于 add(10,20) → 30
// 绑定两个参数,生成无参函数
auto add5_3 = std::bind(add, 5, 3);
std::cout << add5_3() << std::endl; // 等价于 add(5,3) → 8
return 0;
}
用法 2:调整参数顺序
通过占位符的顺序,改变新函数参数的传递顺序。
cpp
#include <iostream>
#include <functional>
int divide(int a, int b) { return a / b; }
int main() {
// 原函数:divide(被除数, 除数)
// 新函数:reverse_divide(除数, 被除数)
auto reverse_divide = std::bind(divide, std::placeholders::_2, std::placeholders::_1);
std::cout << divide(10, 2) << std::endl; // 10/2=5
std::cout << reverse_divide(2, 10) << std::endl; // 10/2=5
return 0;
}
用法 3:绑定类的成员函数
类的普通成员函数有一个隐含的this指针参数,因此绑定成员函数时,第一个参数必须是对象实例(或指针、引用)。
cpp
#include <iostream>
#include <functional>
class Person {
public:
void say_hello(const std::string& name) const {
std::cout << "Hello, " << name << "! I'm " << m_name << std::endl;
}
std::string m_name;
};
int main() {
Person p{"Alice"};
// 绑定成员函数和对象实例
auto say_hello_to = std::bind(&Person::say_hello, &p, std::placeholders::_1);
say_hello_to("Bob"); // 等价于 p.say_hello("Bob")
// 也可以绑定对象本身(值传递,会拷贝对象)
auto say_hello_to_copy = std::bind(&Person::say_hello, p, std::placeholders::_1);
p.m_name = "Charlie";
say_hello_to_copy("Bob"); // 输出 Hello, Bob! I'm Alice(拷贝的对象未改变)
return 0;
}
用法 4:传递引用参数
std::bind 默认对参数进行值传递 。如果需要传递引用,必须使用 std::ref(左值引用)或 std::cref(const 左值引用)。
cpp
#include <iostream>
#include <functional>
void increment(int& x) { x++; }
int main() {
int num = 0;
// 错误:bind默认值传递,会拷贝num,无法修改原变量
// auto bad_inc = std::bind(increment, num);
// bad_inc();
// std::cout << num << std::endl; // 输出0
// 正确:使用std::ref传递引用
auto good_inc = std::bind(increment, std::ref(num));
good_inc();
std::cout << num << std::endl; // 输出1
return 0;
}
四、std::function 与 std::bind 组合使用
这是最常见的用法:用 std::bind 生成适配后的可调用对象,再用 std::function 统一类型,实现回调机制 和策略模式。
示例:实现一个简单的事件处理器
cpp
#include <iostream>
#include <functional>
#include <vector>
// 事件类型
enum class Event {
Click,
KeyPress
};
// 事件处理器:统一接收Event类型的回调
class EventHandler {
public:
using Callback = std::function<void(Event)>;
void register_callback(Callback cb) {
callbacks_.push_back(cb);
}
void trigger_event(Event e) {
for (auto& cb : callbacks_) {
cb(e);
}
}
private:
std::vector<Callback> callbacks_;
};
// 不同签名的回调函数
void on_event(Event e, const std::string& name) {
std::cout << name << " received event: " << static_cast<int>(e) << std::endl;
}
class Logger {
public:
void log_event(Event e) {
std::cout << "Logger: Event " << static_cast<int>(e) << " occurred" << std::endl;
}
};
int main() {
EventHandler handler;
// 绑定普通函数的额外参数
handler.register_callback(std::bind(on_event, std::placeholders::_1, "App1"));
handler.register_callback(std::bind(on_event, std::placeholders::_1, "App2"));
// 绑定成员函数
Logger logger;
handler.register_callback(std::bind(&Logger::log_event, &logger, std::placeholders::_1));
// 触发事件
handler.trigger_event(Event::Click);
return 0;
}
五、常见应用场景
- 回调函数:异步编程、事件驱动架构(如 GUI、网络编程)中,将回调函数注册给框架。
- 策略模式:运行时动态切换算法,无需继承体系。
- 参数适配:将不同签名的函数适配成统一接口,满足框架要求。
- 延迟调用:将函数和参数打包,在合适的时机执行(如线程池任务)。
- 函数柯里化:将多参数函数分解为多个单参数函数,便于函数组合。
六、注意事项与最佳实践
-
优先使用 lambda 表达式 :C++11 之后,lambda 表达式在大多数场景下比
std::bind更简洁、易读、灵活 。特别是复杂的参数绑定和逻辑处理,lambda 的优势明显。cpp// bind版本 auto add10_bind = std::bind(add, 10, std::placeholders::_1); // lambda版本(更清晰) auto add10_lambda = [](int x) { return add(10, x); }; -
避免绑定临时对象:如果绑定的对象是临时对象,当临时对象销毁后,调用绑定后的函数会导致未定义行为。
-
成员函数绑定注意对象生命周期:绑定成员函数时,必须保证对象的生命周期长于绑定后的可调用对象。
-
空值检查 :调用
std::function前,务必检查是否为空,避免抛出异常。 -
性能考量 :
std::function有轻微的运行时开销(类型擦除和虚函数调用),但在绝大多数场景下可以忽略不计。