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_mapstd::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_tstd::is_invocable_v 等特性做模板约束或 SFINAE。

六、实践建议

  1. 优先使用 Lambda + std::function 组合
    • 对于灵活回调接口,std::function 提供最简单的类型擦除;对性能敏感场景,可用模板参数或直接传递函数对象避免开销。
  2. 慎用 std::bind 占位符
    • C++11/14 中 std::bind 方便,但可读性不如 Lambda;在 C++17 及以后,通常用 Lambda 替代。
  3. 使用 std::invoke 统一调用
    • 在模板库或转发可调用对象时,std::invoke 能自动处理成员指针和普通函数,推荐用于通用工具中。
  4. 自定义哈希与比较
    • 当使用 unordered_* 容器存自定义类型时,记得同时提供高质量的 hash 和 == 运算,以避免性能倒退或错误。
  5. 结合类型特性做约束
    • 用 std::is_invocable_vstd::invoke_result_tstd::enable_if_t 等做模板元编程,既可在编译期捕获错误,又能生成更优代码。

通过以上对 <functional> 中函数对象分类、通用适配器、哈希支持与类型推导工具的全面梳理,相信能帮助你在回调设计、自定义容器、元编程等场景中快速选型并写出既简洁又高效的代码。祝编码顺利!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注