5. Lambda与自定义函数对象:仿函数、Lambda与仿函数的对比、何时选择Lambda

好,咱们今天聊点实在的。Lambda 表达式虽然方便,但它并不是 C++ 里唯一能“像函数一样调用”的东西。还有一种老牌技术——仿函数(Functor),也就是重载了 operator() 的类对象。

我个人习惯把仿函数叫做“带状态的函数”。你想想看,普通函数除了参数和全局变量,没法保存自己的“记忆”。但仿函数可以。它是个对象,有成员变量,能记住东西。

5.1 仿函数长什么样?

说白了,仿函数就是一个类,里面重载了 operator()。比如:

class Adder {
public:
    Adder(int inc) : increment(inc) {}

    int operator()(int x) const {
        return x + increment;
    }

private:
    int increment;
};

// 使用
Adder add5(5);
std::cout << add5(10);  // 输出 15

嗯,这里要注意:add5(10) 看起来像函数调用,实际上调用的是 operator()。这就是仿函数名字的由来——模仿函数行为的对象。

我的经验:我在项目中遇到过用仿函数做策略模式的场景。比如一个排序算法,需要根据不同的比较策略来排序。用仿函数可以轻松保存策略参数,比函数指针灵活多了。

5.2 Lambda 与仿函数的对比

Lambda 表达式本质上就是编译器帮你生成的匿名仿函数。但两者在使用上还是有区别的。我整理了一张表,方便你对比:

对比维度 Lambda 表达式 仿函数
代码简洁性 极高,一行搞定 需要定义类,代码量多
捕获状态 通过捕获列表,灵活但有限制 成员变量任意,完全控制
复用性 通常一次使用,难以复用 可以多次实例化,复用性好
类型 匿名类型,每个 Lambda 不同 有明确类型名,可继承
调试友好度 调试器里显示为 lambda 有类名,容易定位
性能 理论上与仿函数一致 理论上与 Lambda 一致

为什么会这样?说白了,Lambda 是“快消品”,用完即走。仿函数是“耐用品”,可以反复使用,甚至还能继承和多态。

核心区别:Lambda 是语法糖,仿函数是底层实现。Lambda 能做的,仿函数都能做;仿函数能做的,Lambda 不一定能做得优雅。

5.3 何时选择 Lambda?

我个人总结了三句话:

  • 一次性的逻辑——用 Lambda。比如给 std::sort 传个比较器,写完就扔,没必要单独定义个类。
  • 需要捕获局部变量——用 Lambda。捕获列表 [=][&] 太方便了,仿函数还得手动传参。
  • 代码量敏感——用 Lambda。几行代码能解决的事,别搞个类出来。
// Lambda 版本:简洁
std::vector<int> v = {3, 1, 4, 1, 5};
int threshold = 3;
auto count = std::count_if(v.begin(), v.end(), 
    [threshold](int x) { return x > threshold; });

// 仿函数版本:啰嗦
class GreaterThan {
public:
    GreaterThan(int t) : threshold(t) {}
    bool operator()(int x) const { return x > threshold; }
private:
    int threshold;
};
auto count2 = std::count_if(v.begin(), v.end(), GreaterThan(threshold));

你看,Lambda 版本明显更清爽。我在实际项目中,90% 的场景都用 Lambda 就够了。

5.4 何时选择仿函数?

那什么时候该用仿函数呢?我遇到过这么几种情况:

  • 需要多次复用同一个逻辑——仿函数可以定义一次,到处实例化。
  • 逻辑复杂,需要多个成员函数辅助——Lambda 只能有一个 operator(),仿函数可以有任意多个辅助方法。
  • 需要继承或多态——仿函数可以是类层次结构的一部分,Lambda 不行。
  • 需要显式类型名——比如作为模板参数传递时,仿函数有名字,Lambda 是匿名的。
我曾经踩过的坑:有一次我用 Lambda 实现了一个复杂的比较逻辑,后来需要在三个不同的地方复用。结果只能复制粘贴代码,改一处还得改三处。如果当初用仿函数,改一个类定义就行了。所以,复用超过两次,就考虑仿函数

5.5 知识体系总览

下面这张图帮你理清 Lambda 和仿函数的关系:

Lambda vs 仿函数 决策树 需要可调用对象? 逻辑是否只使用一次? 是 → 用 Lambda 否 → 继续判断 需要多态/继承/复杂状态? 是 → 用仿函数

这张图的核心逻辑很简单:一次性的、轻量的、捕获局部变量的场景,无脑选 Lambda。需要复用、复杂状态、多态的场景,选仿函数。

5.6 一个实战对比

最后,我拿一个真实项目中的例子来收尾。假设我们要对一个用户列表做过滤,条件是可变的:

// Lambda 版本:适合条件简单且一次性
std::vector<User> users = ...;
int minAge = 18;
auto adults = std::find_if(users.begin(), users.end(),
    [minAge](const User& u) { return u.age >= minAge; });

// 仿函数版本:适合条件复杂且可复用
class AgeFilter {
public:
    AgeFilter(int min, int max) : minAge(min), maxAge(max) {}
    bool operator()(const User& u) const {
        return u.age >= minAge && u.age <= maxAge;
    }
private:
    int minAge;
    int maxAge;
};

// 可以多次使用
AgeFilter filter18to60(18, 60);
AgeFilter filter30to40(30, 40);
auto r1 = std::find_if(users.begin(), users.end(), filter18to60);
auto r2 = std::find_if(users.begin(), users.end(), filter30to40);

你看,Lambda 适合“用完即弃”的场景。仿函数适合“定义一次,多处使用”的场景。没有谁绝对好,关键看场景。

我的建议:新手先从 Lambda 入手,用熟了再考虑仿函数。等你遇到“这个 Lambda 我好像在哪见过”的时候,自然就知道该用仿函数了。

公众号:蓝海资料掘金营,微信deep3321