个人简介
作者简介:全栈研发,具备端到端系统落地能力,专注大模型的压缩部署、多模态理解与 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 的区别
项目 | binder1st | binder2nd |
---|---|---|
固定参数 | 第一个参数 | 第二个参数 |
适配函数 | 二元函数对象 | 二元函数对象 |
适配结果 | 一元函数对象 | 一元函数对象 |
示例 | 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与函数对象适配器的关系》,请留言告诉我!