侯捷 C++ 学习笔记:仿函数适配器与组合器机制

个人简介
在这里插入图片描述
作者简介:全栈研发,具备端到端系统落地能力,专注大模型的压缩部署、多模态理解与 Agent 架构设计。 热爱“结构”与“秩序”,相信复杂系统背后总有简洁可控的可能。
我叫观熵。不是在控熵,就是在观测熵的流动
个人主页:观熵
个人邮箱:privatexxxx@163.com
座右铭:愿科技之光,不止照亮智能,也照亮人心!

专栏导航

观熵系列专栏导航:
AI前沿探索:从大模型进化、多模态交互、AIGC内容生成,到AI在行业中的落地应用,我们将深入剖析最前沿的AI技术,分享实用的开发经验,并探讨AI未来的发展趋势
AI开源框架实战:面向 AI 工程师的大模型框架实战指南,覆盖训练、推理、部署与评估的全链路最佳实践
计算机视觉:聚焦计算机视觉前沿技术,涵盖图像识别、目标检测、自动驾驶、医疗影像等领域的最新进展和应用案例
国产大模型部署实战:持续更新的国产开源大模型部署实战教程,覆盖从 模型选型 → 环境配置 → 本地推理 → API封装 → 高性能部署 → 多模型管理 的完整全流程
TensorFlow 全栈实战:从建模到部署:覆盖模型构建、训练优化、跨平台部署与工程交付,帮助开发者掌握从原型到上线的完整 AI 开发流程
PyTorch 全栈实战专栏: PyTorch 框架的全栈实战应用,涵盖从模型训练、优化、部署到维护的完整流程
深入理解 TensorRT:深入解析 TensorRT 的核心机制与部署实践,助力构建高性能 AI 推理系统
Megatron-LM 实战笔记:聚焦于 Megatron-LM 框架的实战应用,涵盖从预训练、微调到部署的全流程
AI Agent:系统学习并亲手构建一个完整的 AI Agent 系统,从基础理论、算法实战、框架应用,到私有部署、多端集成
DeepSeek 实战与解析:聚焦 DeepSeek 系列模型原理解析与实战应用,涵盖部署、推理、微调与多场景集成,助你高效上手国产大模型
端侧大模型:聚焦大模型在移动设备上的部署与优化,探索端侧智能的实现路径
行业大模型 · 数据全流程指南:大模型预训练数据的设计、采集、清洗与合规治理,聚焦行业场景,从需求定义到数据闭环,帮助您构建专属的智能数据基座
机器人研发全栈进阶指南:从ROS到AI智能控制:机器人系统架构、感知建图、路径规划、控制系统、AI智能决策、系统集成等核心能力模块
人工智能下的网络安全:通过实战案例和系统化方法,帮助开发者和安全工程师识别风险、构建防御机制,确保 AI 系统的稳定与安全
智能 DevOps 工厂:AI 驱动的持续交付实践:构建以 AI 为核心的智能 DevOps 平台,涵盖从 CI/CD 流水线、AIOps、MLOps 到 DevSecOps 的全流程实践。
C++学习笔记?:聚焦于现代 C++ 编程的核心概念与实践,涵盖 STL 源码剖析、内存管理、模板元编程等关键技术
AI × Quant 系统化落地实战:从数据、策略到实盘,打造全栈智能量化交易系统


侯捷 C++ 学习笔记:仿函数适配器与组合器机制


一、引言:STL 的“函数式武器库”

你是否遇到过这样的需求:

  • 你有一个二元比较器 greater<int>,却只传给需要一元函数的 find_if
  • 想对元素做“取反”逻辑,难道要重写一个函数对象?
  • 想组合两个判断条件,不想重复写遍历?

别急,STL 的仿函数适配器与组合器机制就是为此而生的。

侯捷老师在课程中深入讲解了 STL 提供的一系列仿函数适配工具(如 not1bind2ndptr_fun 等),它们构成了早期 STL 中最接近函数式编程的设计部分。

本篇博客将系统梳理:

  • 仿函数适配器的使用场景、机制与源码结构
  • 各种经典适配器(如 binder1st, not1, bind, ptr_fun)的原理
  • 如何使用组合器来构造灵活的算法行为
  • 现代 C++(C++11+)中的替代方案(lambda, std::bind)

二、背景回顾:什么是“仿函数适配器”?

2.1 仿函数 ≠ 适配器

  • 仿函数(Function Object)是行为载体:

    struct Greater {
        bool operator()(int a, int b) const { return a > b; }
    };
    
  • 仿函数适配器(Adaptor)是行为改造器:

    • 改变参数数量
    • 改变返回逻辑
    • 把普通函数变成仿函数
    • 将函数对象变成组合形式

2.2 典型场景

你有 std::greater<int> 是一个 二元函数对象,但你想这样用:

std::find_if(v.begin(), v.end(), std::greater<int>()); // ❌ 错误!find_if 需要一元谓词

怎么办?用适配器把它“绑定”成一元函数。


三、仿函数适配器基础之 binder1st / binder2nd

3.1 bind1st 和 bind2nd 是什么?

它们是早期 STL 中两个用于**“绑定一个参数”**的工具,用来把二元函数适配成一元函数。

bind1st(op, val) => 固定第一个参数
bind2nd(op, val) => 固定第二个参数

3.2 示例:找出大于5的数

std::vector<int> v = {1, 3, 6, 7, 2};
auto it = std::find_if(v.begin(), v.end(), std::bind2nd(std::greater<int>(), 5));

等价逻辑:

greater<int>()(x, 5)  // 即 x > 5

3.3 binder2nd 实现原理(简化版)

template<class Op, class T>
class binder2nd {
public:
    binder2nd(const Op& op, const T& val) : op_(op), val_(val) {}

    template<typename Arg>
    bool operator()(const Arg& x) const {
        return op_(x, val_);
    }

private:
    Op op_;
    T val_;
};

template<class Op, class T>
binder2nd<Op, T> bind2nd(const Op& op, const T& val) {
    return binder2nd<Op, T>(op, val);
}

四、逻辑适配器 not1 / not2:让逻辑更灵活

4.1 问题场景:我想找“不满足条件”的元素

你有一个谓词 is_even(),怎么快速反转它的逻辑?

4.2 not1 使用方式

struct IsEven {
    bool operator()(int x) const { return x % 2 == 0; }
};

std::find_if(v.begin(), v.end(), std::not1(IsEven()));

输出第一个奇数!

4.3 STL 源码结构(not1 简化版)

template<typename Predicate>
class unary_negate {
public:
    explicit unary_negate(Predicate p) : pred(p) {}

    template<typename T>
    bool operator()(const T& x) const {
        return !pred(x);
    }

private:
    Predicate pred;
};

template<typename Predicate>
unary_negate<Predicate> not1(Predicate p) {
    return unary_negate<Predicate>(p);
}

not2 也是类似,只不过适配二元谓词。


五、ptr_fun:让普通函数适配为仿函数

5.1 场景:如何把 C 风格函数用于 STL 算法?

bool is_odd(int x) { return x % 2 != 0; }

std::count_if(v.begin(), v.end(), std::ptr_fun(is_odd)); // OK!

ptr_fun 就是把函数指针转成仿函数对象。

5.2 源码简化版:

template<typename Ret, typename Arg>
class pointer_to_unary_function {
public:
    explicit pointer_to_unary_function(Ret (*f)(Arg)) : func(f) {}
    Ret operator()(Arg x) const { return func(x); }

private:
    Ret (*func)(Arg);
};

template<typename Ret, typename Arg>
pointer_to_unary_function<Ret, Arg> ptr_fun(Ret (*f)(Arg)) {
    return pointer_to_unary_function<Ret, Arg>(f);
}

六、组合器机制:组合多个仿函数形成新行为

侯捷老师在课程中还提到:“组合器机制”是函数式 STL 的极致应用。

例如:

// 判断是否 x > 10 && x % 2 == 0
std::find_if(v.begin(), v.end(),
    And(IsGreater(10), IsEven()));

虽然早期 STL 没有提供全套组合器,但通过如下写法可以实现:

template<typename F1, typename F2>
struct And {
    F1 f1; F2 f2;
    And(F1 a, F2 b) : f1(a), f2(b) {}
    bool operator()(int x) const {
        return f1(x) && f2(x);
    }
};

七、现代 C++ 的替代方案:lambda 和 std::bind

7.1 lambda 完全替代旧式适配器

旧写法:

std::find_if(v.begin(), v.end(), std::bind2nd(std::greater<int>(), 5));

新写法:

std::find_if(v.begin(), v.end(), [](int x){ return x > 5; });

7.2 std::bind 的使用

using namespace std::placeholders;

std::find_if(v.begin(), v.end(), std::bind(std::greater<int>(), _1, 5));

虽然 std::bind 功能更强,但 lambda 更直观、可读性更高,已成为主流。


八、实战演练:构建一个灵活组合过滤器

目标:筛选出所有是偶数且大于某个阈值的元素

template<typename T>
struct IsEven {
    bool operator()(T x) const { return x % 2 == 0; }
};

template<typename T>
struct GreaterThan {
    T threshold;
    GreaterThan(T t) : threshold(t) {}
    bool operator()(T x) const { return x > threshold; }
};

template<typename F1, typename F2>
struct And {
    F1 f1; F2 f2;
    And(F1 a, F2 b) : f1(a), f2(b) {}
    bool operator()(int x) const {
        return f1(x) && f2(x);
    }
};

使用:

std::vector<int> v = {1,2,3,4,5,6,7,8,9};
auto it = std::find_if(v.begin(), v.end(), And(IsEven<int>(), GreaterThan<int>(4)));
std::cout << *it << std::endl;  // 输出6

九、侯捷老师的设计理念

侯捷老师评价仿函数适配器系统:

“这是一个非常优雅的 C++ 模板设计典范。使用 traits + adapter 技术,让算法达到近乎函数式的灵活性。”

关键设计理念:

  • 使用模板类和 operator() 组合行为
  • 提供统一接口让算法不关心行为的实现方式
  • 避免虚函数和继承开销,全部在编译期静态决议

十、总结:适配器与组合器的实战价值

工具/机制功能使用时代
bind1st/2nd把二元仿函数适配成一元函数C++98
not1/not2取反逻辑适配器C++98
ptr_fun把普通函数指针包装成仿函数C++98
std::bind泛化绑定任意参数C++11+
lambda一站式函数表达式C++11+
组合器(And/Or)构建逻辑组合任意

它们让算法的行为变得 解耦、灵活、组合优雅,极大提高了 STL 的表达力。


十一、附加练习(建议动手)

  1. 实现一个 Or<F1, F2> 函数组合器
  2. 实现 bind_frontbind_back 模拟 std::bind 行为
  3. not1 + bind2nd 组合模拟 x <= 5 的谓词

十二、后记

虽然现代 C++ 已经有了 lambda、range、concept 等更强的表达方式,但理解 STL 的仿函数适配器与组合器机制,依然是理解泛型编程设计的黄金钥匙。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

观熵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值