C++ 标准库 functional
下面对 C++ 标准库中 <functional>
头文件提供的主要组件做一次系统、详尽的梳理,包括函数对象(Function Object)、通用包装器与适配器、哈希支持、以及与调用和类型推导相关的工具,并配以典型示例与实践建议。
一、概述
<functional>
定义了若干通用的函数对象类型(即重载了operator()
的类模板),以及用于包装、适配、调用函数或可调用对象的工具。- 这些组件不仅支持 STL 容器的定制比较、排序、哈希等功能,也常用于信号/槽、回调、任务调度等场景。
二、函数对象(Function Objects)分类
1. 算术运算器(Arithmetic)
模板 | 功能 | 相当于 |
---|---|---|
std::plus<T> | 加法 | a + b |
std::minus<T> | 减法 | a - b |
std::multiplies<T> | 乘法 | a * b |
std::divides<T> | 除法 | a / b |
std::modulus<T> | 取模 | a % b |
std::negate<T> | 取负 | -a |
#include <functional>
#include <vector>
#include <numeric>
#include <iostream>
int main() {
std::vector<int> v{1,2,3,4};
// 使用 std::multiplies 在 accumulate 中计算乘积
int prod = std::accumulate(v.begin(), v.end(), 1, std::multiplies<>());
std::cout << "Product = " << prod << "\n"; // 输出 Product = 24
}
2. 比较运算器(Comparisons)
模板 | 功能 | 相当于 |
---|---|---|
std::equal_to<T> | 等于 | a == b |
std::not_equal_to<T> | 不等 | a != b |
std::greater<T> | 大于 | a > b |
std::less<T> | 小于 | a < b |
std::greater_equal<T> | 大于等于 | a >= b |
std::less_equal<T> | 小于等于 | a <= b |
可用于容器排序或自定义比较器:
#include <set>
#include <functional>
std::set<int, std::greater<>> desc_set{5,1,4,2,3};
// 元素按降序排列
3. 逻辑运算器(Logical)
模板 | 功能 | 相当于 |
---|---|---|
std::logical_and<T> | 逻辑与 | a && b |
std::logical_or<T> | 逻辑或 | `a |
std::logical_not<T> | 逻辑非 | !a |
三、通用包装器与适配器
1. std::function
- 类型擦除的通用可调用对象包装器,可保存任意可调用目标(函数指针、Lambda、成员函数指针、其它函数对象)。
- 支持拷贝、赋值和储存到容器,但会有小量运行时开销。
#include <functional>
#include <iostream>
void greet(const std::string& name) { std::cout << "Hello, " << name << "\n"; }
int main() {
std::function<void(const std::string&)> f = &greet;
f("Alice"); // 调用 greet
f = [](const std::string& s){ std::cout << "Hi, " << s << "\n"; };
f("Bob"); // 调用 Lambda
}
2. std::bind
与 占位符
- 生成一个绑定表达式,将可调用对象与部分实参或位置参数绑定在一起,返回新的可调用对象。
#include <functional>
#include <iostream>
int add(int a, int b, int c) { return a + b + c; }
using namespace std::placeholders; // _1, _2, …
int main() {
auto f = std::bind(add, _2, 10, _1);
// f(x, y) 等价于 add(y, 10, x)
std::cout << f(1, 2) << "\n"; // 输出 13
}
3. std::mem_fn
与 std::invoke
std::mem_fn(&Class::member)
:将成员函数或成员变量指针包装成可调用对象。std::invoke(f, args…)
:以统一方式调用任意可调用目标(支持成员指针、函数指针、函数对象等)。
#include <functional>
#include <iostream>
#include <vector>
struct S { void show(int x) const { std::cout << x << "\n"; } };
int main() {
S s;
auto mf = std::mem_fn(&S::show);
mf(s, 42); // 等同于 s.show(42)
// std::invoke 同样能调用
std::invoke(&S::show, s, 99);
}
4. 引用包装器(std::reference_wrapper
)
- 用于将引用“装箱”以存入容器或传递给
std::function
,可以通过.get()
或operator()
取出。
#include <functional>
#include <vector>
#include <iostream>
int main() {
int x = 10;
std::vector<std::reference_wrapper<int>> vr;
vr.push_back(std::ref(x));
vr[0].get() = 20;
std::cout << x << "\n"; // 输出 20
}
四、哈希支持
std::hash<T>
模板对常见类型(算术类型、指针、字符串、标准容器)提供特化,实现了一个无符号整数哈希值。- 可用于构造
std::unordered_map
、std::unordered_set
等。
#include <unordered_map>
#include <string>
std::unordered_map<std::string,int> freq;
freq["apple"] = 3;
freq["banana"] = 2;
用户自定义类型可通过特化 std::hash<MyType>
或传入自定义哈希函数对象来支持。
五、类型推导与结果类型
1. std::result_of
(C++11/14,已弃用)
- 推导给定可调用类型与实参列表调用后的结果类型:
typename std::result_of<F(Args...)>::type
2. std::invoke_result
(C++17 起)
- 更通用地推导
std::invoke(f, args...)
的返回类型:typename std::invoke_result<F, Args...>::type
- 可结合
std::enable_if_t
、std::is_invocable_v
等特性做模板约束或 SFINAE。
六、实践建议
- 优先使用 Lambda +
std::function
组合- 对于灵活回调接口,
std::function
提供最简单的类型擦除;对性能敏感场景,可用模板参数或直接传递函数对象避免开销。
- 对于灵活回调接口,
- 慎用
std::bind
占位符- C++11/14 中
std::bind
方便,但可读性不如 Lambda;在 C++17 及以后,通常用 Lambda 替代。
- C++11/14 中
- 使用
std::invoke
统一调用- 在模板库或转发可调用对象时,
std::invoke
能自动处理成员指针和普通函数,推荐用于通用工具中。
- 在模板库或转发可调用对象时,
- 自定义哈希与比较
- 当使用
unordered_*
容器存自定义类型时,记得同时提供高质量的hash
和==
运算,以避免性能倒退或错误。
- 当使用
- 结合类型特性做约束
- 用
std::is_invocable_v
、std::invoke_result_t
、std::enable_if_t
等做模板元编程,既可在编译期捕获错误,又能生成更优代码。
- 用
通过以上对 <functional>
中函数对象分类、通用适配器、哈希支持与类型推导工具的全面梳理,相信能帮助你在回调设计、自定义容器、元编程等场景中快速选型并写出既简洁又高效的代码。祝编码顺利!