火柴人战争遗产3
413.8MB · 2026-02-21
选择 std::function、模板还是裸函数指针,核心在于权衡灵活性、性能和代码复杂度。以下是详细的决策指南:
是否需要存储回调?(类成员、容器、跨函数传递)
├── 是 → 是否需要类型擦除?(存储不同类型但相同签名的可调用对象)
│ ├── 是 → std::function(或 std::function_ref C++23)
│ └── 否 → 模板(如 std::vector<具体类型>)
└── 否 → 编译时确定类型?
├── 是 → 模板(零开销,最优性能)
└── 否 → 裸函数指针(C接口、极简场景)
template<typename F>)— 首选,如果可能适用场景:
// 推荐:零开销抽象
template<typename Func>
void execute(Func&& func) {
func(); // 直接内联,无间接调用开销
}
// 使用
execute([]{ std::cout << "lambdan"; });
execute(my_functor);
execute(free_function);
优点:
缺点:
std::function — 需要类型擦除时适用场景:
std::vector<std::function<...>>)// 推荐:需要存储的场景
class TaskScheduler {
std::vector<std::function<void()>> tasks_; // 存储不同类型任务
public:
void add(std::function<void()> task) { // 接受任何可调用对象
tasks_.push_back(std::move(task));
}
void run() {
for (auto& t : tasks_) t();
}
};
// 可以混合添加:
scheduler.add([]{ /* lambda */ });
scheduler.add(std::bind(&Class::method, &obj));
scheduler.add(free_function);
优点:
缺点:
bad_function_call性能优化:
// C++23 引入 std::function_ref:轻量级、非拥有、零分配
void process(std::function_ref<void(int)> callback); // 只读引用,不存储
适用场景:
// 推荐:C API 回调
void qsort(void* base, size_t n, size_t size,
int (*cmp)(const void*, const void*)); // C 标准库
// 推荐:信号处理
void (*signal(int sig, void (*func)(int)))(int);
// 不推荐:现代 C++ 应用开发
void register_callback(void (*cb)(int)); // 无法接受 lambda!
优点:
缺点:
void* 转换)| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 算法中的自定义比较/操作 | 模板 | std::sort(begin, end, cmp) 式接口 |
| 事件/回调系统 | std::function | 需要存储不同者 |
| 依赖注入/策略模式 | 模板 或 std::function | 模板编译期确定,std::function 运行时切换 |
| 跨 DLL 边界 | std::function 或 裸指针 | 注意 ABI 兼容性 |
| C API 封装 | 裸指针 | 被迫使用 |
| 高频调用(>100万次/秒) | 模板 | 避免虚函数开销 |
| 延迟执行/任务队列 | std::function | 必须存储 |
| 配置驱动(运行时决定行为) | std::function | 运行时多态 |
// C++23 std::function_ref:只读、非拥有、零分配
void process(std::function_ref<int(int)> op);
// 对比:
// std::function : 拥有,可存储,有分配开销
// std::function_ref : 不拥有,轻量,仅借用
// 模板 : 编译期确定,最优性能,无存储能力
std::function:需要放入容器或类成员时std::function_ref 可能成为"需要引用但不存储"场景的最佳选择核心原则:先尝试模板,遇到存储需求再降级到 std::function,裸指针作为最后手段。