通俗的来讲function是函数包装器,而bind绑定器的作用是将二元或者多元的仿函数降低维度。
C++11 引入的std::function和std::bind是函数式编程的核心组件,彻底改变了 C++ 中可调用对象的处理方式。它们解决了传统 C++ 中可调用对象类型不统一、参数无法灵活适配的问题,是实现回调、策略模式、延迟调用的基础。
一、核心概念
std::function:可调用对象的类型统一器。它是一个模板类,能够包装任何可调用对象(普通函数、函数指针、lambda、仿函数、成员函数指针),并将其转换为具有统一签名的类型。std::bind:参数适配器。它是一个函数模板,能够将一个可调用对象与部分参数绑定,生成一个新的可调用对象。支持固定参数值、调整参数顺序、延迟调用等功能。
二、std::function 详解
2.1 基本语法
#include <functional> // 模板参数格式:返回值类型(参数类型列表) std::function<返回值(参数1类型, 参数2类型, ...)> func;2.2 包装所有可调用对象
std::function最大的价值在于类型擦除:它能将不同类型但签名相同的可调用对象,统一为同一个std::function类型。
#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异常。std::function<void()> empty_func; if (empty_func) { empty_func(); // 不会执行 } - 赋值与拷贝:支持拷贝构造、移动构造和赋值操作,可作为函数参数和返回值传递。
- 容器存储:由于类型统一,可将多个不同类型的可调用对象存入同一个容器。
std::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 基本语法
#include <functional> // 绑定可调用对象和参数 auto new_func = std::bind(可调用对象, 参数1, 参数2, ...);- 参数:可以是固定值,也可以是占位符(
std::placeholders::_1,_2, ...,_N)。 - 占位符:代表新可调用对象的第 N 个参数。例如
_1表示新函数的第一个参数,_2表示第二个参数。 - 返回值:一个未指定类型的可调用对象,通常用
auto接收,或用std::function包装。
3.2 核心用法
用法 1:绑定固定参数(柯里化)
将原函数的部分参数固定为常量,生成一个参数更少的新函数。
#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:调整参数顺序
通过占位符的顺序,改变新函数参数的传递顺序。
#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指针参数,因此绑定成员函数时,第一个参数必须是对象实例(或指针、引用)。
#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 左值引用)。
#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统一类型,实现回调机制和策略模式。
示例:实现一个简单的事件处理器
#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 的优势明显。// 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有轻微的运行时开销(类型擦除和虚函数调用),但在绝大多数场景下可以忽略不计。