首页 > 编程笔记 > C++笔记 阅读:15

C++ std::function模板类的用法(附带实例)

std::function 是 C++ 里的一个模板类,用于存储、管理和调用任何类型的可调用对象。它是 C++11 中引入的,属于 <functional> 头文件。

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...)> 的用法如下:
例如,std::function<int(float, double)> 表示一个可以接收任何 float 和 double 参数并返回 int 的可调用对象。这使得 std::function 成为一个非常通用的类型,可以用于替代具体的函数指针,提供更高的灵活性和功能性。

接下来,我们将查看 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(C++11 至 C++17) 专门化的 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 内部使用一个模板类实现类型消除,该模板类抽象出可调用对象的调用行为和存储方式。这种抽象主要通过以下两个组件实现:

2) 构造和调用过程


这种实现方式允许 std::function 作为一个通用的函数引用容器,存储几乎任何类型的可调用对象,并提供统一的调用接口。

3) 生命周期和存储管理

std::function 对象的生命周期管理是自动的,但它涉及动态内存分配以存储可调用对象的副本,特别是当可调用对象的大小超过内部预分配的小缓冲区时(这个阈值具体是多少并没有统一标准,而是依赖于具体的库实现和平台)。

这种动态分配可能影响性能,尤其在高频调用的场景中:

4) 性能考量

较小的可调用对象:
较大的可调用对象:
使用小对象优化的主要目的是减少因频繁动态内存分配和释放而带来的性能开销,对于大多数日常使用场景,这种优化能显著提高性能。然而,对于较大的可调用对象,动态内存的使用是不可避免的,这在某些高性能敏感的应用中可能成为一个考虑因素。

虽然 std::function 提供了极大的灵活性,但其类型消除带来的间接性可能导致调用开销的增加,特别是在性能敏感的环境中。因此,在设计接口和选择使用 std::function 时,应仔细权衡其便利性与潜在的性能成本。

相关文章