文章目录
1. 什么是仿函数?
仿函数(Functor),也称为函数对象(Function Object),是C++中一种特殊的对象,它可以像函数一样被调用。本质上,仿函数是一个类或结构体,它重载了函数调用运算符operator()。
仿函数是STL中的重要概念之一,可以用于 实现自定义的比较、排序、映射 等操作,使代码更具灵活性和可重用性。
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
2. 仿函数与普通函数的区别
特性 | 普通函数 | 仿函数 |
---|---|---|
状态保持 | 无 | 可以保持状态 |
内联优化 | 可能 | 更易被编译器内联 |
多台支持 | 有限 | 支持完整面向对象特性 |
作为模版参数 | 仅函数指针 | 可直接作为模版参数 |
3. 标准库中的仿函数
C++标准库提供了许多有用的仿函数,主要位于 “functional” 头文件中:
- 算术运算:plus, minus, multiplies, divides, modulus, negate
- 比较运算:equal_to, not_equal_to, greater, less, greater_equal, less_equal
- 逻辑运算:logical_and, logical_or, logical_not
- 位运算:bit_and, bit_or, bit_xor
这里我们用std::plus()来创建一个加法仿函数,并将其存储在std::function对象中,然后可以像函数一样去调用它。
#include <iostream>
#include <functional>
int main()
{
std::function<int(int, int)> myFunc = std::plus<int>(); // 使用标准库的仿函数
int result = myFunc(5, 3); // 调用仿函数
std::cout << "Result: " << result << std::endl;
return 0;
}
4. 仿函数的优势
4.1 状态保持
仿函数可以包含成员变量,因此可以在调用之间保持状态:
class Counter {
int count = 0;
public:
void operator()(int x) {
std::cout << "Element #" << ++count << ": " << x << "\n";
}
};
int main() {
std::vector<int> v = {10, 20, 30};
std::for_each(v.begin(), v.end(), Counter());
}
4.2 可定制性
通过模板和继承,仿函数可以实现高度可定制的行为:
在这个示例中,我们创建了两个不同的仿函数,一个用于加法(MyAdditionFunc),一个用于减法( MySubtractionFunc ),它们可以根据需要进行切换。
class MyAdditionFunc
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class MySubtractionFunc
{
public:
int operator()(int a, int b)
{
return a - b;
}
};
int main()
{
MyAdditionFunc add;
MySubtractionFunc subtract;
int result1 = add(5, 3); // 8
int result2 = subtract(5, 3); // 2
std::cout << "Result of addition: " << result1 << std::endl;
std::cout << "Result of subtraction: " << result2 << std::endl;
return 0;
}
4.3 性能优势
在某些情况下,使用仿函数可能会导致性能开销,因为它们引入了额外的函数调用。与普通函数相比,仿函数可能会有一些微小的性能损失。但在 绝大多数情况下,这种性能损失非常小,通常可以忽略不计。在性能要求非常高的特定应用中,可以使用内联函数或其他优化手段来减小性能损失。
5. 现代C++中的仿函数
5.1 Lambda表达式
C++11引入的 Lambda 表达式本质上是匿名仿函数的语法糖:
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 4); // 输出7
5.2 通用仿函数
使用模板和 auto 参数使仿函数更通用:
struct GenericAdd {
template <typename T, typename U>
auto operator()(T t, U u) const {
return t + u;
}
};
int main() {
GenericAdd add;
std::cout << add(3, 4.5); // 输出7.5
}
6. 仿函数的高级应用(使用C++2020标准库及以上版本)
6.1 函数组合
仿函数可以组合形成更复杂的行为:
template <typename F, typename G>
class Compose {
F f;
G g;
public:
// 构造函数:存储 f 和 g
Compose(F f, G g)
: f(f), g(g)
{}
// operator() 允许对象像函数一样被调用
template <typename X>
auto operator()(X x) const {
return f(g(x)); // 先计算 g(x),再计算 f(g(x))
}
};
int main() {
auto square = [](int x) { return x * x; };
auto increment = [](int x) { return x + 1; };
// Compose(f, g) 表示 f(g(x)),即先执行 g,再执行 f
auto square_then_increment = Compose(increment, square); // 先平方,再加 1
std::cout << square_then_increment(3); // 输出 10 (3² + 1)
// 如果想先 increment 再 square,可以交换顺序
auto increment_then_square = Compose(square, increment); // 先加 1,再平方
std::cout << "\n" << increment_then_square(3); // 输出 16 ((3 + 1)²)
}
6.2 惰性求值
仿函数可以实现惰性求值模式:
class LazyValue {
// function 可以将任何类型的可调用元素 (例如函数和函数对象 )包装到可复制对象中的类,并且其类型仅取决于其调用签名(而不取决于可调用元素类型本身)。
std::function<int()> generator;
// optional 管理一个可选 的容纳值,既可以存在也可以不存在的值。
mutable std::optional<int> cache;
public:
LazyValue(std::function<int()> g) : generator(g) {}
operator int() const {
if (!cache) {
cache = generator();
}
return *cache;
}
};
int expensive_computation() {
// 模拟耗时计算
// sleep_for 当前线程的执行将停止,直到从现在开始至少经过 rel_time 为止。其他线程继续执行。
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main() {
// 延迟42毫秒再运行程序
LazyValue lazy(expensive_computation);
// 计算尚未发生
std::cout << "Value: " << lazy; // 此时进行计算
cout << endl;
std::cout << "Again: " << lazy; // 使用缓存值
cout << endl;
}
7. 总结
仿函数是C++中强大而灵活的工具,它们:
- 比普通函数更强大(可以保持状态)
- 比函数指针更安全(类型安全)
- 比虚函数更高效(静态多态)
- 与现代C++特性(lambda、模板)完美结合
掌握仿函数将显著提升你的C++编程能力,特别是在泛型编程和标准库使用方面。