std::functional 是 C++ 标准库中一个非常强大的工具,它提供了一种**类型擦除(type erasure)**机制,让你能够存储、传递和调用任何可调用对象(callable)。
核心作用
1. 统一的可调用对象包装器
std::function 可以包装任何可调用实体,只要签名匹配:
- 普通函数
- Lambda 表达式
- 函数对象(仿函数)
- 成员函数(通过
std::bind或 lambda) - 甚至其他
std::function
2. 类型擦除
隐藏具体类型,只暴露接口。这使得你可以:
- 在容器中存储不同类型的可调用对象
- 作为函数参数接受任意可调用对象
- 实现回调机制而不需要模板
主要使用场景
场景 1:回调函数(Callbacks)
cpp
#include <functional>
#include <iostream>
class Button {
std::function<void()> onClick_;
public:
void setOnClick(std::function<void()> callback) {
onClick_ = callback;
}
void click() { if (onClick_) onClick_(); }
};
// 使用
Button btn;
btn.setOnClick([]() { std::cout << "Clicked!\n"; });
btn.click();
场景 2:策略模式 / 算法注入
cpp
#include <functional>
#include <vector>
#include <algorithm>
void processData(std::vector<int>& data,
std::function<bool(int)> filter,
std::function<int(int)> transform) {
// 先过滤
data.erase(std::remove_if(data.begin(), data.end(),
[&](int x) { return !filter(x); }), data.end());
// 再转换
for (auto& x : data) x = transform(x);
}
// 使用
std::vector<int> nums = {1, 2, 3, 4, 5, 6};
processData(nums,
[](int x) { return x % 2 == 0; }, // 只保留偶数
[](int x) { return x * x; } // 平方
);
场景 3:事件系统 / 观察者模式
cpp
#include <functional>
#include <vector>
#include <string>
class EventSystem {
std::vector<std::function<void(const std::string&)>> listeners_;
public:
void subscribe(std::function<void(const std::string&)> listener) {
listeners_.push_back(listener);
}
void emit(const std::string& event) {
for (auto& listener : listeners_) {
listener(event);
}
}
};
// 使用
EventSystem events;
events.subscribe([](const std::string& e) {
std::cout << "Logger: " << e << "\n";
});
events.subscribe([](const std::string& e) {
std::cout << "Metrics: recorded " << e << "\n";
});
场景 4:延迟执行 / 任务队列
cpp
#include <functional>
#include <queue>
#include <iostream>
class TaskQueue {
std::queue<std::function<void()>> tasks_;
public:
void addTask(std::function<void()> task) {
tasks_.push(task);
}
void runAll() {
while (!tasks_.empty()) {
tasks_.front()();
tasks_.pop();
}
}
};
// 使用
TaskQueue queue;
queue.addTask([]() { std::cout << "Task 1\n"; });
queue.addTask([]() { std::cout << "Task 2\n"; });
queue.runAll();
场景 5:绑定成员函数
cpp
#include <functional>
#include <iostream>
class Calculator {
public:
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
};
// 使用 std::bind
Calculator calc;
auto addFunc = std::bind(&Calculator::add, &calc,
std::placeholders::_1, std::placeholders::_2);
std::cout << addFunc(3, 4); // 7
// 或者使用 lambda(更推荐,性能更好)
auto multiplyFunc = [&calc](int a, int b) {
return calc.multiply(a, b);
};
std::function vs 模板 vs 裸函数指针
| 特性 | std::function |
模板 | 裸函数指针 |
|---|---|---|---|
| 类型擦除 | ✅ 是 | ❌ 否 | ✅ 是(但只能指向函数) |
| 存储 Lambda | ✅ 可以 | ✅ 可以 | ❌ 不行(除非无捕获) |
| 运行时开销 | 有(虚函数调用) | 无 | 无 |
| 编译时类型检查 | 弱(运行时可能抛 bad_function_call) |
强 | 强 |
| 存储在容器中 | ✅ 容易 | ❌ 难(需要类型擦除) | ✅ 可以 |
最佳实践
-
优先使用模板:如果不需要存储可调用对象,用模板参数更高效
cpp// 更好:零开销 template<typename Func> void execute(Func&& f) { f(); } // 有开销:类型擦除 void execute(std::function<void()> f) { f(); } -
检查空状态 :调用前确保
std::function不为空cppif (func) func(); // 或 if (func != nullptr) -
注意开销 :
std::function通常使用小对象优化(SOO),但大对象会堆分配 -
C++23 替代 :考虑使用
std::move_only_function(仅移动)或std::copyable_function
std::functional 的核心价值在于灵活性------当你需要在运行时决定调用什么、或者需要在容器中存储可调用对象时,它是不可或缺的工具。