C++ std::function模板类的用法(附带实例)
std::function 是 C++ 里的一个模板类,用于存储、管理和调用任何类型的可调用对象。它是 C++11 中引入的,属于 <functional> 头文件。
std::function 的最大优点是可以包装任何满足调用签名的可调用对象,包括普通函数、Lambda 表达式、函数对象和绑定表达式。这意味着,无论函数的具体形式如何,std::function 都能以统一的方式对其进行处理。
std::function 的模板声明如下:
例如,std::function<int(float, double)> 表示一个可以接收任何 float 和 double 参数并返回 int 的可调用对象。这使得 std::function 成为一个非常通用的类型,可以用于替代具体的函数指针,提供更高的灵活性和功能性。
接下来,我们将查看 std::function 的成员类型和功能,以便读者更好地理解和使用这个类模板。
std::function 成员如下表所示:
表中详细列出了 std::function 类模板的关键成员和函数,这些都是在使用时需要了解和考虑的特性。
这意味着,程序员不仅无需关心可调用实体具体类型的情况下统一处理函数调用,还在编译时期进行类型检查,确保赋予的可调用对象符合预期的签名,从而减少运行时的类型错误。
此外,在需要函数回调或事件驱动编程(如GUI或网络应用程序)时,使用 std::function 可以简化函数的管理和调用过程,使代码更加清晰和易于维护。
下面将详细探讨其实现原理、生命周期管理以及与存储和性能相关的考量。
这种实现方式允许 std::function 作为一个通用的函数引用容器,存储几乎任何类型的可调用对象,并提供统一的调用接口。
这种动态分配可能影响性能,尤其在高频调用的场景中:
较大的可调用对象:
使用小对象优化的主要目的是减少因频繁动态内存分配和释放而带来的性能开销,对于大多数日常使用场景,这种优化能显著提高性能。然而,对于较大的可调用对象,动态内存的使用是不可避免的,这在某些高性能敏感的应用中可能成为一个考虑因素。
虽然 std::function 提供了极大的灵活性,但其类型消除带来的间接性可能导致调用开销的增加,特别是在性能敏感的环境中。因此,在设计接口和选择使用 std::function 时,应仔细权衡其便利性与潜在的性能成本。
std::function 的最大优点是可以包装任何满足调用签名的可调用对象,包括普通函数、Lambda 表达式、函数对象和绑定表达式。这意味着,无论函数的具体形式如何,std::function 都能以统一的方式对其进行处理。
std::function 的模板声明如下:
#include <functional> // 未定义的模板声明 template<class> class function; // 未定义 // 定义的模板 template<class R, class... Args> class function<R (Args...)>; // 定义从 C++11 开始std::function<R(Args...)> 的用法如下:
- R 是返回类型。
- Args... 是参数类型的列表。
例如,std::function<int(float, double)> 表示一个可以接收任何 float 和 double 参数并返回 int 的可调用对象。这使得 std::function 成为一个非常通用的类型,可以用于替代具体的函数指针,提供更高的灵活性和功能性。
接下来,我们将查看 std::function 的成员类型和功能,以便读者更好地理解和使用这个类模板。
std::function 成员如下表所示:
类型/功能 | 说明 |
---|---|
成员类型 | |
result_type | 返回类型R |
argument_type(C++17 中已弃用,C++20 中已移除) | 当 Args... 仅包含一个类型 T 时的参数类型 T |
first_argument_type(C++17 中已弃用,C++20 中已移除) | 当 Args... 包含两个类型时的第一个参数类型 T1 |
second_argument_type(C++17 中已弃用,C++20 中已移除) | 当 Args... 包含两个类型时的第二个参数类型 T2 |
成员函数 | |
构造函数 | 构造一个新的 std::function 实例 |
析构函数 | 销毁 std::function 实例 |
operator= | 赋值一个新目标 |
swap | 交换内容 |
assign(C++17 中已移除) | 赋值一个新目标 |
operator bool | 检查是否包含目标 |
operator() | 调用目标 |
目标访问 | |
target_type | 获取存储目标的 typeid |
target | 获取指向存储目标的指针 |
非成员函数 | |
std::swap(std::function) | 专门化的 std::swap 算法 |
operator== / operator!=(C++20 中已移除) | 比较 std::function 与 nullptr |
辅助类 | |
std::uses_allocator |
专门化的 std::uses_allocator 类型特性 |
表中详细列出了 std::function 类模板的关键成员和函数,这些都是在使用时需要了解和考虑的特性。
C++ std::function的主要作用
std::function 在 C++ 中扮演着重要的角色,主要体现在以下两个方面:1) 统一封装与类型安全的包装
std::function 提供了一种通用且类型安全的方式来存储和调用各种类型的可调用实体,如普通函数、Lambda表达式、函数指针、成员函数指针以及函数对象(functors)。这意味着,程序员不仅无需关心可调用实体具体类型的情况下统一处理函数调用,还在编译时期进行类型检查,确保赋予的可调用对象符合预期的签名,从而减少运行时的类型错误。
2) 强化函数式编程与简化回调管理
std::function 的灵活性允许函数作为参数传递给其他函数或作为返回值,支持更高级的函数式编程范式。此外,在需要函数回调或事件驱动编程(如GUI或网络应用程序)时,使用 std::function 可以简化函数的管理和调用过程,使代码更加清晰和易于维护。
C++ std::function实例
下面创建一个 std::function 容器,用来存储并调用不同类型的可调用对象。每个可调用对象都将执行一个简单的操作,并返回一个结果,以便观察 std::function 的行为。#include <iostream> #include <functional> #include <string> // 普通函数 int multiply(int x, int y) { return x * y; } // 函数对象 struct Divider { int operator()(int x, int y) { if (y != 0) return x / y; return 0; // 避免除以0 } }; int main() { // 使用 std::function 定义一个可调用对象的容器 std::function<int(int, int)> func; // 将普通函数存储到 std::function func = multiply; std::cout << "乘法结果: " << func(10, 5) << std::endl; // 应输出 50 // 将 Lambda 表达式存储到 std::function func = [](int x, int y) { return x - y; }; std::cout << "减法结果: " << func(10, 5) << std::endl; // 应输出 5 // 将函数对象存储到 std::function func = Divider(); std::cout << "除法结果: " << func(10, 5) << std::endl; // 应输出 2 return 0; }运行结果为:
乘法结果: 50
减法结果: 5
除法结果: 2
C++ std::function 的实现机制
std::function 的能力基于一种被称为类型消除的技术,它允许将不同的可调用对象统一到一个通用接口下进行管理和调用。下面将详细探讨其实现原理、生命周期管理以及与存储和性能相关的考量。
1) 类型消除的核心机制
std::function 内部使用一个模板类实现类型消除,该模板类抽象出可调用对象的调用行为和存储方式。这种抽象主要通过以下两个组件实现:- 函数指针:std::function 维护一个通用的函数指针,指向一个内部函数,该函数负责调用实际存储的可调用对象。
- 包装器:std::function 包含一个包装器,通常是一个指向动态分配存储的void*类型指针。这个包装器实现了对原始可调用对象类型的封装和转换,确保可以通过函数指针安全调用。
2) 构造和调用过程
- 构造时的处理:在 std::function 的构造过程中,它将接收的可调用对象封装在一个类型消除的容器内。这通常涉及创建一个包装器实例,该实例持有指向具体可调用对象的指针。
- 调用操作:调用 std::function 的 operator() 时,内部的函数指针用来触发实际的可调用对象。这是通过包装器实现的,包装器负责正确的类型转换和调用逻辑。
这种实现方式允许 std::function 作为一个通用的函数引用容器,存储几乎任何类型的可调用对象,并提供统一的调用接口。
3) 生命周期和存储管理
std::function 对象的生命周期管理是自动的,但它涉及动态内存分配以存储可调用对象的副本,特别是当可调用对象的大小超过内部预分配的小缓冲区时(这个阈值具体是多少并没有统一标准,而是依赖于具体的库实现和平台)。这种动态分配可能影响性能,尤其在高频调用的场景中:
- 小对象优化(small object optimization,SOO):对于小型可调用对象,std::function 通常使用内部的固定大小存储空间,避免动态内存分配,从而提高性能。
- 大对象处理:对于较大的可调用对象,std::function 将使用动态内存来存储对象,这可能带来额外的性能开销。
4) 性能考量
较小的可调用对象:- 简单的函数指针:只包含一个指向函数的指针,非常小,通常是几字节(通常与机器架构的指针大小相同,如4或8字节)。
- 无状态的Lambda表达式:不捕获任何外部变量的Lambda表达式,其大小通常与函数指针相当。
- 轻量级函数对象:不包含数据成员或仅包含少量数据成员的函数对象。
较大的可调用对象:
- 捕获多个或大型数据的 Lambda 表达式:如果Lambda 表达式捕获了多个数据成员或捕获了大型对象(如大数组、容器等),它的大小会增加。
- 具有多个数据成员的函数对象:如果一个函数对象包含多个或复杂的数据成员(如字符串、向量、映射等),它的大小也会相应较大。
- 成员函数指针与对象指针的组合:这种情况常见于绑定类成员函数时。尽管 std::bind 和成员函数指针自身不是很大,但如果绑定时捕获了类实例(非引用捕获),则可能导致整体大小较大。
使用小对象优化的主要目的是减少因频繁动态内存分配和释放而带来的性能开销,对于大多数日常使用场景,这种优化能显著提高性能。然而,对于较大的可调用对象,动态内存的使用是不可避免的,这在某些高性能敏感的应用中可能成为一个考虑因素。
虽然 std::function 提供了极大的灵活性,但其类型消除带来的间接性可能导致调用开销的增加,特别是在性能敏感的环境中。因此,在设计接口和选择使用 std::function 时,应仔细权衡其便利性与潜在的性能成本。