8. 函数对象:从概念到实战
函数对象,也叫仿函数(Functor)。说白了,就是一个重载了 operator() 的类。你想想看,类实例化出来的对象,居然能像函数一样被调用——这就是函数对象的核心。
我刚开始学 STL 时,觉得这东西有点多余。明明有普通函数,为什么还要搞个类出来?直到我在项目中遇到一个场景:需要给 sort 算法传一个比较规则,而且这个规则还要带状态。嗯,普通函数做不到,函数对象却可以。
8.1 函数对象的基本概念
先看一个最简单的例子:
#include <iostream>
// 定义一个函数对象
class MyAdd {
public:
int operator()(int a, int b) {
return a + b;
}
};
int main() {
MyAdd add; // 创建对象
int result = add(3, 5); // 像函数一样调用
std::cout << result; // 输出 8
return 0;
}
看到没?add(3, 5) 这个写法,跟普通函数一模一样。但 add 其实是个对象。这就是函数对象最直观的样子。
8.2 为什么需要函数对象?
我个人习惯用函数对象,主要有三个理由:
- 可以携带状态:普通函数只能靠全局变量或静态变量,函数对象可以在成员变量里存数据
- 内联优化更好:编译器更容易内联 operator(),性能比函数指针好
- STL 算法天然支持:很多算法都接受函数对象作为参数
举个例子,我想统计一个容器里大于某个阈值的元素个数。用普通函数你得写死阈值,用函数对象就可以灵活设置:
#include <iostream>
#include <vector>
#include <algorithm>
class GreaterThan {
private:
int threshold_;
public:
GreaterThan(int t) : threshold_(t) {}
bool operator()(int value) {
return value > threshold_;
}
};
int main() {
std::vector<int> data = {1, 5, 8, 3, 10, 2};
int count = std::count_if(data.begin(), data.end(), GreaterThan(5));
std::cout << "大于5的元素个数: " << count; // 输出 2
return 0;
}
我在项目中遇到过类似的需求:需要根据用户输入的阈值动态过滤数据。用函数对象,阈值作为构造参数传进去,非常优雅。
8.3 谓词:返回 bool 的函数对象
谓词(Predicate)是函数对象的一个特例——它的 operator() 返回 bool 值。STL 算法里大量使用谓词来做条件判断。
一元谓词
接受一个参数,返回 bool。比如判断一个数是否为偶数:
class IsEven {
public:
bool operator()(int n) {
return n % 2 == 0;
}
};
// 使用
std::vector<int> v = {1, 2, 3, 4, 5};
auto it = std::find_if(v.begin(), v.end(), IsEven());
// it 指向 2
二元谓词
接受两个参数,返回 bool。常用于排序和比较:
class Greater {
public:
bool operator()(int a, int b) {
return a > b;
}
};
// 降序排序
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), Greater());
// 结果: 5, 4, 3, 1, 1
8.4 内建函数对象
STL 已经帮我们封装好了常用的函数对象,放在 <functional> 头文件里。我个人觉得,能用内建的就不用自己写,省事又不容易出错。
| 分类 | 内建函数对象 | 说明 |
|---|---|---|
| 算术类 | plus<T> | 加法 |
| minus<T> | 减法 | |
| multiplies<T> | 乘法 | |
| divides<T> | 除法 | |
| modulus<T> | 取模 | |
| negate<T> | 取反 | |
| 关系类 | equal_to<T> | 等于 |
| not_equal_to<T> | 不等于 | |
| greater<T> | 大于 | |
| less<T> | 小于 | |
| greater_equal<T> | 大于等于 | |
| less_equal<T> | 小于等于 | |
| 逻辑类 | logical_and<T> | 逻辑与 |
| logical_or<T> | 逻辑或 |
举个例子,用内建的 greater 实现降序排序:
#include <functional>
#include <vector>
#include <algorithm>
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), std::greater<int>());
// 结果: 5, 4, 3, 1, 1
你看,一行代码搞定,比自己写 Greater 类简洁多了。
8.5 函数对象 vs 函数指针 vs Lambda
很多初学者会问:这三者到底怎么选?我根据经验总结一下:
- 函数指针:C 风格,性能一般,不推荐在 C++ 新代码中使用
- 函数对象:需要携带状态时首选,性能好,代码可读性强
- Lambda 表达式:C++11 引入,临时用一下很方便,但复杂逻辑还是建议用函数对象
8.6 知识体系总览
下面这张图帮你理清函数对象的整体脉络:
核心要点回顾:
- 函数对象是重载了 operator() 的类,可以像函数一样调用
- 谓词是返回 bool 的函数对象,分一元和二元两种
- STL 内建了算术、关系、逻辑三大类函数对象,在 <functional> 中
- 函数对象比函数指针更灵活,比 Lambda 更适合复杂逻辑
函数对象这东西,说白了就是 C++ 里「万物皆可对象」思想的体现。把行为封装成对象,你就能享受面向对象的一切好处:状态、继承、多态。我在实际项目中,经常用函数对象来做策略模式,把不同的算法逻辑封装成不同的函数对象,调用方只需要传入合适的对象即可。这种设计,既灵活又解耦。
公众号:蓝海资料掘金营,微信deep3321