侯捷 C++ 课程学习笔记:Binder2nd —— STL 仿函数与绑定器的经典魅力

个人简介
在这里插入图片描述
作者简介:全栈研发,具备端到端系统落地能力,专注大模型的压缩部署、多模态理解与 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++ 课程学习笔记:Binder2nd —— STL 仿函数与绑定器的经典魅力


✨ 前言:为什么我们要深入理解 binder2nd

函数对象配适器,是 STL 泛型算法背后的变形金刚。

侯捷老师在讲解 STL 中的函数对象与函数适配器时,花了大量时间讲述 binder2nd 的设计与作用。这并不是因为 binder2nd 多么炫酷,而是因为它所体现的 泛型编程哲学编译期类型推导艺术,是我们学习现代 C++ 必须深刻理解的核心理念。

尽管在 C++11 之后,std::bind 和 lambda 表达式已经大行其道,binder2nd 也早已被标记为 deprecated。但理解它的原理依然具有极高的学习价值,尤其对理解函数对象、算法解耦和 STL 设计具有指导意义。


🧠 一、STL 中的仿函数与函数对象适配器回顾

1. 什么是函数对象(function object)?

函数对象是重载了函数调用运算符 operator() 的类对象,可以像函数一样被调用,但本质上是一个类。

示例:
struct Add {
    int operator()(int a, int b) const {
        return a + b;
    }
};

使用方式:

Add add;
std::cout << add(2, 3);  // 输出:5

它比普通函数的好处是:

  • 可携带状态;
  • 可以作为类型参与模板参数;
  • 可组合、可定制、可偏函数绑定。

2. 为什么需要适配器(Adaptor)?

STL 的算法和容器是独立设计的,为了让算法能够“看懂”容器的数据结构和用户定义的行为,我们引入了一整套“适配器”机制。

函数对象适配器,就是把“用户自定义的行为”转换成算法可识别的接口。例如:

  • not1 将一元谓词逻辑取反;
  • ptr_fun 将函数指针转换成仿函数;
  • bind1st/bind2nd 让一个二元函数变成一元函数;

🎯 二、binder2nd:让函数变得“部分应用”的魔法器

1. 什么是 binder2nd?

binder2nd 是 STL 中的一个函数适配器,它将一个二元函数对象的第二个参数固定住,转化成一个一元函数对象。

2. 应用场景:

你想用一个只接受“一个参数”的算法(比如 find_if),但是你的函数是需要两个参数的,例如大于某个值、除以某个数是否整除、比较器等 —— 就可以用 binder2nd


📚 三、真实案例:用 binder2nd 实现查找大于 10 的元素

示例代码:

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>

int main() {
    std::vector<int> v = {3, 7, 12, 5, 20, 8};

    // 使用 greater<int> 和 binder2nd
    auto it = std::find_if(v.begin(), v.end(), std::bind2nd(std::greater<int>(), 10));

    if (it != v.end())
        std::cout << "Found: " << *it << std::endl;
    else
        std::cout << "Not found.\n";

    return 0;
}

分析:

  • std::greater<int>() 是一个二元函数对象,定义了 operator()(a, b)a > b
  • std::bind2nd(std::greater<int>(), 10) 把第二个参数绑定为 10,生成一个只需要一个参数的新函数对象 f(x) = x > 10
  • find_if 就能直接调用这个函数了。

这就是经典的“部分应用”。


🔬 四、binder2nd 的源码剖析

以下是 libstdc++ 中 binder2nd 的简化源码实现:

template <class Operation>
class binder2nd
    : public std::unary_function<typename Operation::first_argument_type,
                                 typename Operation::result_type> {
protected:
    Operation op;
    typename Operation::second_argument_type value;

public:
    binder2nd(const Operation& x, const typename Operation::second_argument_type& y)
        : op(x), value(y) {}

    typename Operation::result_type
    operator()(const typename Operation::first_argument_type& x) const {
        return op(x, value);
    }
};

// 辅助函数
template <class Operation, class T>
inline binder2nd<Operation> bind2nd(const Operation& op, const T& x) {
    return binder2nd<Operation>(op, x);
}

解读:

  • binder2nd 是一个类模板,接收一个二元仿函数 Operation
  • 继承自 std::unary_function,表明它是个一元函数(适配后的)
  • operator() 中把第一个参数传入,第二参数由构造时指定,形成 op(x, value) 的形式
  • bind2nd() 是语法糖,用于生成 binder2nd 对象

⚙️ 五、仿函数 + 适配器的组合能力展示

示例:寻找所有大于等于 15 的元素并打印

#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>

int main() {
    std::vector<int> vec = {12, 5, 17, 20, 6, 8};

    auto it = std::find_if(vec.begin(), vec.end(),
                std::bind2nd(std::greater_equal<int>(), 15));

    if (it != vec.end())
        std::cout << "First >= 15: " << *it << std::endl;

    return 0;
}

这里 greater_equal<int>() 表示 a >= b,而 bind2nd 变成了 x >= 15


🧠 六、binder1st 与 binder2nd 的区别

项目binder1stbinder2nd
固定参数第一个参数第二个参数
适配函数二元函数对象二元函数对象
适配结果一元函数对象一元函数对象
示例less<int>()(10, x)less<int>()(x, 10)
使用场景固定左操作数固定右操作数

代码示例对比:

std::bind1st(std::less<int>(), 10); // 10 < x
std::bind2nd(std::less<int>(), 10); // x < 10

🔄 七、替代品:C++11 的 bind 与 lambda 表达式

随着 C++11 的发布,std::bind 和 lambda 完全替代了传统的 binder1st / binder2nd。

bind 替代写法:

#include <functional>

std::find_if(vec.begin(), vec.end(), std::bind(std::greater<int>(), std::placeholders::_1, 10));

lambda 替代写法:

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

lambda 更短、更直观、更易调试,也避免了复杂的类型推导问题。


🔍 八、深入理解:仿函数、类型萃取与 traits 模板

binder2nd 实现中,我们发现了 std::unary_function 的身影,它定义了如下类型:

typedef Arg argument_type;
typedef Result result_type;

这些类型信息是为了让 泛型算法能够自动推导参数与返回值类型。C++11 中已移除 unary_function / binary_function,改用 decltype + traits 模板。


🧱 九、自己动手:手写一个自定义 binder2nd

template <typename BinaryFunc, typename Arg2>
class my_binder2nd {
    BinaryFunc func;
    Arg2 bound_value;

public:
    my_binder2nd(const BinaryFunc& f, const Arg2& val)
        : func(f), bound_value(val) {}

    template <typename Arg1>
    auto operator()(const Arg1& arg1) const {
        return func(arg1, bound_value);
    }
};

template <typename BinaryFunc, typename Arg2>
my_binder2nd<BinaryFunc, Arg2> my_bind2nd(BinaryFunc f, Arg2 val) {
    return my_binder2nd<BinaryFunc, Arg2>(f, val);
}

使用方式:

std::greater<int> gt;
auto bound = my_bind2nd(gt, 10);
std::cout << bound(12); // true

🧠 十、侯捷老师课程的理解升华

侯捷老师在课程中指出,binder2nd 是对函数对象进行“偏函数应用”的一个原始实践,体现的是一种面向算法的编程方式,其设计哲学如下:

  • 算法只关心行为;
  • 行为被参数化为函数对象;
  • 函数对象可通过“适配器”完成灵活组合;
  • 一切在编译期完成,类型安全,零运行时开销。

🧪 十一、常见面试题与实战应用

1. bind2nd 能绑定成员函数吗?

不能。bind2nd 只能用于普通仿函数;成员函数绑定需要使用 std::bind 或 lambda。

2. 使用 bind2nd + 自定义比较器

struct MyCompare {
    bool operator()(int a, int b) const {
        return (a % 10) < (b % 10);
    }
};

使用:

auto it = std::find_if(v.begin(), v.end(), std::bind2nd(MyCompare(), 5));

✅ 总结:binder2nd 的价值不在“使用”,而在“理解”

说明
定义二元函数对象转为一元函数
本质函数适配器
用法STL 算法中进行部分应用
特点编译期类型安全,零开销
缺点不如 lambda 直观;类型复杂
替代C++11 的 lambda / bind

📚 学习建议与延伸方向

  • 实现 binder1st, not1, not2 等适配器,加深理解;
  • 深入探索 C++11 的 std::bind, std::function
  • 阅读 Boost.Bind、Boost.Lambda 的设计哲学;
  • 探索现代 C++ 的函数式编程风格与表达式模板。

🎉 结语:从 binder2nd 看见 STL 的优雅

“C++ 不只是语法的集合,它是思想的融合。”

binder2nd 只是 STL 仿函数系统中的冰山一角,却体现了模板编程、类型萃取、算法解耦、行为组合等众多精髓。真正掌握它,远远不止会用而已。

愿你在泛型编程的世界里越走越深,也期待我们一起探索更多 STL 的奇迹!


如果你喜欢这篇内容,欢迎点赞、评论、收藏。如果你希望我继续写《not1/not2深度解析》或《lambda与函数对象适配器的关系》,请留言告诉我!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

观熵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值