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 会根据存储的不同可调用对象类型,调用相应的实现。这种行为是运行时决定的,因此属于运行时多态。

相关推荐
ghie909017 分钟前
基于MATLAB的TLBO算法优化实现与改进
开发语言·算法·matlab
恋爱绝缘体117 分钟前
2020重学C++重构你的C++知识体系
java·开发语言·c++·算法·junit
wuk99817 分钟前
VSC优化算法MATLAB实现
开发语言·算法·matlab
Z1Jxxx1 小时前
加密算法加密算法
开发语言·c++·算法
乌萨奇也要立志学C++1 小时前
【洛谷】递归初阶 三道经典递归算法题(汉诺塔 / 占卜 DIY/FBI 树)详解
数据结构·c++·算法
vyuvyucd1 小时前
C++引用:高效编程的别名利器
算法
鱼跃鹰飞2 小时前
Leetcode1891:割绳子
数据结构·算法
️停云️2 小时前
【滑动窗口与双指针】不定长滑动窗口
c++·算法·leetcode·剪枝·哈希
charlie1145141912 小时前
嵌入式现代C++教程: 构造函数优化:初始化列表 vs 成员赋值
开发语言·c++·笔记·学习·嵌入式·现代c++
IT=>小脑虎2 小时前
C++零基础衔接进阶知识点【详解版】
开发语言·c++·学习