C++仿函数应在需保存状态或被STL算法接受时使用;必须重载const限定的operator(),支持内联与零成本抽象,类型明确、可复用、易测试,优于lambda的场景包括泛型模板参数、多次配置复用及接口契约表达。

直接说结论:C++仿函数不是“用不用”的问题,而是“什么时候该用类对象封装调用逻辑”——核心就两条:需要保存状态,或需要被STL算法(如std::sort、std::transform)接受为可调用参数时,operator()才是最自然、最高效的选择。
怎么写一个能用的仿函数类
必须重载operator(),且语法和普通函数几乎一致;但它是成员函数,所以能访问私有成员。常见错误是忘了加const限定符,导致无法传给要求const可调用对象的STL算法(比如std::sort默认比较器)。
- 返回类型、参数列表按需定义,和普通函数一样
- 如果不需要修改对象状态(绝大多数情况),务必加
const,例如:int operator()(int a, int b) const - 构造函数用来初始化内部状态,比如阈值、偏移量、计数器初值
- 别在
operator()里做耗时操作——它可能被反复调用,且编译器期望能内联
示例:带初始偏移的加法器
struct AddOffset {
int offset;
AddOffset(int o) : offset(o) {}
int operator()(int x) const { return x + offset; }
};
// 使用
AddOffset add10(10);
int res = add10(5); // 得到15
为什么不能直接用普通函数或lambda
普通函数无法携带状态;lambda虽简洁,但每次定义都是独立类型,无法作为模板参数统一传递(除非用auto或std::function,但后者有运行时开销)。而仿函数类型明确、零成本抽象、天然支持模板推导。
- 要多次复用同一逻辑+不同配置?仿函数类实例可拷贝、可存储、可传参,比lambda更可控
- 要写泛型算法模板,接收“任意可调用对象”?仿函数类型名清晰,
template接收它毫无压力void algo(F f) - 要让
std::bind或旧式适配器(如std::not1)工作?它们只认特定签名的仿函数,不接受lambda(C++11前无lambda,这些设施就是为仿函数设计的)
在STL算法里传仿函数要注意什么
STL算法(如std::sort、std::find_if)内部会对传入的仿函数对象进行拷贝——这是最容易踩的坑:如果你的仿函数有指针成员或非平凡析构逻辑,没写好拷贝构造/赋值,行为就未定义。
- 优先用值语义:成员全是基本类型或标准容器,让编译器自动生成安全拷贝
- 避免裸指针、
new分配资源;真需要动态资源,用std::shared_ptr或std::unique_ptr管理 - 如果仿函数只是临时用(比如一行
std::sort(v.begin(), v.end(), Greater{})),用struct Greater { bool operator()(int a, int b) const { return a > b; } };这种轻量定义,完全没问题 - 别把有副作用的逻辑(如打印、IO、修改全局变量)塞进
operator()——STL不保证调用次数和顺序
仿函数和lambda到底怎么选
没有绝对优劣,看上下文。lambda适合一次性、简单、局部逻辑;仿函数适合需复用、带状态、或作为接口契约的场景。关键点在于:lambda本质是编译器生成的匿名仿函数类,所以二者底层同源,但表达意图不同。
- 写个排序规则,只在这一个
std::sort里用?用lambda:[&threshold](int x) { return x > threshold; } - 要封装一个可配置的过滤器,后续还可能用于
std::transform和日志统计?写仿函数类,名字即文档 - 需要把“可调用对象”存进容器(如
std::vector<:function>>)?这时反而该用std::function包装,而不是直接存仿函数类型(因类型各异)
真正容易被忽略的是:仿函数的类型名本身是有意义的接口。当你看到Counter、Multiplier、CaseInsensitiveCompare这样的类名,你就知道它做什么;而lambda只是一段代码,没名字、没复用性、也不易测试。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











