C/C++|std::function 浅度解析

std::function 是 C++ 标准库中的一个通用多态函数包装器。它可以存储、复制和调用任意可调用目标(函数、lambda 表达式、绑定表达式或其他函数对象)。 std::function 占有固定尺寸的内存,这是因为它的实现方式决定了这一点。让我们深入探讨这一点。

std::function 的实现原理

std::function 通常使用类型擦除和小对象优化(Small Object Optimization, SOO)来实现这一点。

  1. 类型擦除:
  • std::function 使用类型擦除来存储不同类型的可调用对象。这意味着它通过一个固定大小的存储空间和一个指向这些对象的虚表(vtable)来实现多态性。
  • 类型擦除允许 std::function 在运行时处理各种不同的类型,而不需要知道这些类型的具体细节。
  1. 小对象优化(SOO):
  • 对于小对象(通常是指尺寸较小且可以直接存储在 std::function 内部的对象),std::function 会直接在其内部存储这些对象。这避免了动态内存分配的开销。
  • 对于大对象(超出 std::function 内部存储容量的对象),std::function 会在堆上分配内存,并在内部存储一个指向这些对象的指针。

std::function 为什么占有固定尺寸的内存

由于 std::function 使用了类型擦除和小对象优化,其内部实现通常包含以下几个部分:

  • 一个指向实际存储对象的指针或存储小对象的内部缓冲区。
  • 一个指向虚表的指针,用于多态调用。
  • 一些额外的元数据,用于管理存储和调用。

这意味着,无论存储的对象是多大或多小,std::function 的实例总是占用固定大小的内存,以包含这些指针和元数据。

内存布局的示例

假设我们有以下 std::function 声明:

cpp 复制代码
std::function<void()> func;

其内部可能包含如下内容:

  1. 指向可调用对象的指针 或 内部缓冲区:

    • 如果对象足够小,可以直接存储在内部缓冲区中。
    • 如果对象较大,则存储一个指向该对象的指针。
  2. 虚表指针:

    • 虚表指针指向一组函数,这些函数用于操作实际存储的对象(调用、复制、销毁等)。
  3. 元数据:

    • 用于管理存储对象的信息,如对象大小、类型信息等。

无论我们存储的是一个普通函数指针,一个小型 lambda 表达式,还是一个大型函数对象,std::function 实例的大小都是固定的

示例代码

cpp 复制代码
#include <iostream>
#include <functional>

void exampleFunction() {
    std::cout << "Hello from function!" << std::endl;
}

int main() {
    // 存储普通函数指针
    std::function<void()> func1 = exampleFunction;
    
    // 存储lambda表达式
    std::function<void()> func2 = []() {
        std::cout << "Hello from lambda!" << std::endl;
    };
    
    // 存储大对象
    struct LargeFunctor {
        void operator()() const {
            std::cout << "Hello from large functor!" << std::endl;
        }
        int data[100];
    };
    std::function<void()> func3 = LargeFunctor();

    // 调用
    func1();
    func2();
    func3();

    std::cout << "Size of func1: " << sizeof(func1) << std::endl;
    std::cout << "Size of func2: " << sizeof(func2) << std::endl;
    std::cout << "Size of func3: " << sizeof(func3) << std::endl;

    return 0;
}

结果如下:

cpp 复制代码
Hello from function!
Hello from lambda!
Hello from large functor!
Size of func1: 32
Size of func2: 32
Size of func3: 32

我们可以很明显得看出三个 std::function 对象大小都是一样的。

小总结:

  • std::function 的大小是固定的,因为它使用类型擦除和小对象优化来处理不同类型的可调用对象。
  • 这种固定大小的内存布局使得 std::function 可以有效地存储和管理多种不同类型的可调用对象,同时提供统一的接口来调用这些对象。
  • 无论存储的对象是函数指针、小型 lambda 表达式,还是大型函数对象,std::function 的实例大小都是固定的。

std::function 实现运行时多态

运行时多态(dynamic polymorphism)指的是在程序运行时决定调用哪个具体的函数实现。通常通过虚函数和继承来实现。std::function 通过类型擦除和虚表机制提供了类似的功能,使得它可以在运行时处理不同类型的可调用对象。

cpp 复制代码
#include <iostream>
#include <functional>

// 一个接受 std::function<void(int)> 类型参数的函数
void invokeWithFive(const std::function<void(int)>& func) {
    func(5);  // 调用传入的可调用对象,并传递参数5
}

int main() {
    // 使用普通函数指针
    void (*funcPtr)(int) = [](int x) { std::cout << "Function pointer: " << x << std::endl; };
    invokeWithFive(funcPtr);

    // 使用 lambda 表达式
    auto lambda = [](int x) { std::cout << "Lambda: " << x << std::endl; };
    invokeWithFive(lambda);

    // 使用函数对象
    struct Functor {
        void operator()(int x) const {
            std::cout << "Functor: " << x << std::endl;
        }
    };
    Functor functor;
    invokeWithFive(functor);

    return 0;
}

在运行时,当我们调用 invokeWithFive(funcPtr)、invokeWithFive(lambda) 和 invokeWithFive(functor) 时,std::function 会根据存储的不同可调用对象类型,调用相应的实现。这种行为是运行时决定的,因此属于运行时多态。

相关推荐
Crossoads9 分钟前
【C++掌中宝】类和对象(一):类的定义和实例化
开发语言·c++·机器学习·ajax·数据挖掘
寂柒34 分钟前
C++——vector
开发语言·c++
penguin_bark35 分钟前
1658. 将 x 减到 0 的最小操作数
算法·leetcode·职场和发展
Li小李同学Li36 分钟前
【C++11】线程库
开发语言·c++
Catherine12131437 分钟前
算法打卡 Day34(贪心算法)-分发饼干 + 摆动序列 + 最大子序和
数据结构·c++·算法·leetcode·贪心算法
小强不秃头1 小时前
Linux C——网络编程
linux·c语言·开发语言
一丝晨光1 小时前
标准输入输出
java·c++·python·c#·go·c·io
界面开发小八哥2 小时前
MFC扩展库BCGControlBar Pro v35.1 - 增强工具栏编辑器功能等
c++·mfc·bcg·界面控件·ui开发
踏过山河,踏过海2 小时前
在Windows系统上安装的 Arrow C++ 库
开发语言·c++
qzy06212 小时前
C++启动其它进程的方式
开发语言·c++