C++中推荐用template + F&&参数接收任意可调用对象,支持函数指针、lambda(含捕获)和functor,零成本抽象;函数指针模板适用于无捕获场景且需极致性能;std::function适合需存储或类型不确定的场合。

在C++中,可以把回调函数类型(函数指针、函数对象或lambda)作为模板参数传入,但具体方式取决于回调的“可调用性”和是否需要捕获上下文。核心思路是:用auto模板参数(C++17起)接收任意可调用对象,或用显式函数指针/仿函数类型模板参数。
用 auto 模板参数接收任意可调用对象(推荐)
这是最通用、最现代的方式,支持函数指针、lambda(含捕获)、functor等,且无需提前声明类型。
template<typename F>
void process_data(int x, F&& callback) {
int result = x * 2;
std::forward<F>(callback)(result); // 完美转发调用
}
<p>// 使用示例
int main() {
// 普通函数
auto func = [](int v) { std::cout << "lambda: " << v << '\n'; };
process_data(5, func); // OK</p><pre class="brush:php;toolbar:false;">// 带捕获的 lambda
int offset = 10;
process_data(5, [offset](int v) { std::cout << "captured: " << v + offset << '\n'; });
// 函数指针
void print(int v) { std::cout << "func ptr: " << v << '\n'; }
process_data(5, print);}
用函数指针类型作为模板参数(无捕获、类型明确)
适用于只接受普通函数或不带捕获的lambda(可隐式转为函数指针),类型安全且零开销,但无法处理捕获型lambda。
template<typename R, typename... Args>
void process_with_fp(R(*callback)(Args...)) {
// 调用示例:假设 callback 接收 int,返回 void
if constexpr (std::is_same_v<R, void> && sizeof...(Args) == 1 &&
std::is_same_v<std::tuple_element_t<0, std::tuple<Args...>>, int>) {
callback(42);
}
}
<p>// 使用
void my_handler(int x) { std::cout << "handled: " << x << '\n'; }
process_with_fp(my_handler);
// process_with_fp([](int x){}); // ❌ 错误:带捕获 lambda 不能转成函数指针
</p>用 std::function 包装(运行时类型擦除,灵活性高)
不是模板参数,但常被用来替代——把回调作为普通函数参数,内部用std::function存储。适合回调类型不确定或需存储/传递的场景。
立即学习“C++免费学习笔记(深入)”;
#include <functional>
<p>void process_stdfunc(std::function<void(int)> callback) {
callback(100);
}</p><p>int main() {
process_stdfunc([](int v) { std::cout << v << '\n'; });
process_stdfunc([](int v) { std::cout << "with capture: " << v * 2 << '\n'; });
}
</p>注意事项与选择建议
-
优先用
template<typename f></typename>+F&&参数:零成本抽象,支持所有可调用对象,配合std::forward避免拷贝。 - 避免把 lambda 当模板非类型参数(NTTP):C++20 允许某些字面量 lambda 作 NTTP,但限制极多(无捕获、仅字面量运算),实用性低,不推荐常规使用。
- 函数指针模板适合嵌入式或极致性能场景:无类型擦除开销,但牺牲灵活性。
- std::function 适合需要存储、延迟调用或类型混合的场合:有小开销(虚调用或小对象优化),但接口统一。










