std::function 与 C语言函数指针详解

问题1:std::function 就是替换了 C语言的函数指针吗?

** 不完全是,std::function` 是一个更强大的包装器,它可以包装函数指针,但不仅仅是函数指针的替换。

问题2:function 里面就是函数指针指向的函数地址吗?

:不完全是。 std::function 内部可能包含函数指针,但也可能包含其他类型的可调用对象(函数对象、lambda表达式、成员函数指针等)。


对比

1. C语言函数指针

// C语言函数指针
typedef void (*FuncPtr)(int);

void myFunction(int x) {
    printf("Value: %d\n", x);
}

int main() {
    FuncPtr ptr = myFunction;  // 直接存储函数地址
    ptr(10);  // 通过函数指针调用
    
    return 0;
}

特点

  • ✅ 直接存储函数地址(内存中的代码地址)
  • ✅ 轻量级,开销小
  • ✅ 类型固定,只能指向特定签名的函数
  • ❌ 只能指向全局函数静态函数
  • ❌ 不能指向成员函数(需要额外处理)
  • ❌ 不能存储函数对象(functor)
  • ❌ 不能存储lambda表达式(C语言不支持)

2. C++ std::function

#include <functional>
#include <iostream>

// 使用 std::function
using FuncType = std::function<void(int)>;

void myFunction(int x) {
    std::cout << "Value: " << x << std::endl;
}

class Functor {
public:
    void operator()(int x) {
        std::cout << "Functor: " << x << std::endl;
    }
};

int main() {
    // 1. 可以包装函数指针
    FuncType f1 = myFunction;
    f1(10);
    
    // 2. 可以包装函数对象(functor)
    Functor functor;
    FuncType f2 = functor;
    f2(20);
    
    // 3. 可以包装lambda表达式
    FuncType f3 = [](int x) {
        std::cout << "Lambda: " << x << std::endl;
    };
    f3(30);
    
    // 4. 可以包装成员函数(需要bind)
    class MyClass {
    public:
        void memberFunc(int x) {
            std::cout << "Member: " << x << std::endl;
        }
    };
    MyClass obj;
    FuncType f4 = std::bind(&MyClass::memberFunc, &obj, std::placeholders::_1);
    f4(40);
    
    return 0;
}

特点

  • ✅ 可以包装多种类型的可调用对象
  • ✅ 统一的接口,类型擦除
  • ✅ 可以存储函数对象、lambda、成员函数等
  • ❌ 有额外的开销(类型擦除、动态分配)
  • ❌ 比函数指针更重(通常有虚函数调用开销)

std::function 内部实现原理

概念模型

std::function<void(int)>
    │
    ├─> [类型擦除层]
    │   └─> 隐藏具体类型,提供统一接口
    │
    └─> [实际存储的可调用对象]
        │
        ├─> 情况1:函数指针
        │   └─> void (*)(int)  // 直接存储函数地址
        │
        ├─> 情况2:函数对象(Functor)
        │   └─> 存储对象实例 + operator() 调用
        │
        ├─> 情况3:Lambda表达式
        │   └─> 编译器生成的匿名类对象
        │
        └─> 情况4:成员函数指针
            └─> 存储对象指针 + 成员函数指针

简化实现示意

// std::function 的简化实现概念(非真实代码)
template<typename Signature>
class function {
private:
    // 类型擦除:使用基类指针存储不同类型的可调用对象
    class CallableBase {
    public:
        virtual ~CallableBase() {}
        virtual void call(/* 参数 */) = 0;
    };
    
    template<typename T>
    class CallableImpl : public CallableBase {
        T callable;  // 实际的可调用对象(函数指针、函数对象、lambda等)
    public:
        CallableImpl(const T& c) : callable(c) {}
        void call(/* 参数 */) override {
            callable(/* 参数 */);  // 调用实际对象
        }
    };
    
    CallableBase* impl;  // 指向具体实现的指针

public:
    template<typename T>
    function(const T& callable) {
        impl = new CallableImpl<T>(callable);
    }
    
    void operator()(/* 参数 */) {
        impl->call(/* 参数 */);
    }
    
    ~function() {
        delete impl;
    }
};

关键点

  1. 类型擦除:使用虚函数和多态隐藏具体类型
  2. 动态分配:通常需要在堆上分配内存存储可调用对象
  3. 间接调用:通过虚函数调用,有额外开销

性能对比

函数指针调用

void func(int x) { /* ... */ }
void (*ptr)(int) = func;

// 调用:直接跳转到函数地址
ptr(10);  
// 汇编级别:CALL [地址] 或 JMP [地址]
// 开销:几乎为零(一次间接跳转)

std::function 调用

std::function<void(int)> f = func;

// 调用:通过虚函数表间接调用
f(10);
// 汇编级别:
// 1. 加载对象指针
// 2. 加载虚函数表指针
// 3. 通过虚函数表查找函数地址
// 4. 调用函数
// 开销:比函数指针多几次内存访问

性能差异

  • 函数指针:最快,几乎零开销
  • std::function:稍慢,有虚函数调用开销(通常可以忽略)

实际示例对比

示例1:存储函数指针

// C语言风格
void callback(int x) {
    std::cout << x << std::endl;
}

void (*c_style)(int) = callback;  // 直接存储函数地址
c_style(10);

// C++ std::function
std::function<void(int)> cpp_style = callback;  // 包装函数指针
cpp_style(10);

// 此时 std::function 内部确实存储了函数指针

示例2:存储Lambda

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

// C语言函数指针:❌ 无法存储lambda(C语言不支持lambda)
// void (*ptr)(int) = lambda;  // 编译错误

// std::function:✅ 可以存储
std::function<void(int)> f = lambda;
f(10);

// 此时 std::function 内部存储的是编译器生成的匿名类对象

示例3:存储函数对象

class Adder {
    int value;
public:
    Adder(int v) : value(v) {}
    void operator()(int x) {
        std::cout << x + value << std::endl;
    }
};

Adder adder(5);

// C语言函数指针:❌ 无法存储函数对象
// void (*ptr)(int) = adder;  // 编译错误

// std::function:✅ 可以存储
std::function<void(int)> f = adder;
f(10);  // 输出 15

// 此时 std::function 内部存储的是 Adder 对象的副本

内存布局对比

函数指针的内存布局

内存地址         内容
0x1000          [函数代码开始]
0x1001          ...
0x2000          [函数代码结束]

0x3000          [函数指针变量]
                值:0x1000  ← 直接存储函数地址

std::function 的内存布局(简化)

内存地址         内容
0x4000          [std::function 对象]
                成员1:vtable指针 (0x5000)
                成员2:可调用对象存储区
                
0x5000          [虚函数表]
                函数1:call() 函数地址 (0x6000)
                
0x6000          [call() 实现]
                实际调用存储的可调用对象
                
0x7000          [实际可调用对象]
                如果是函数指针:存储函数地址
                如果是lambda:存储捕获的变量 + 函数代码
                如果是functor:存储对象数据

使用建议

何时使用函数指针?

// ✅ 适合场景:
// 1. 性能要求极高
// 2. 只需要指向全局/静态函数
// 3. C兼容性要求
// 4. 简单的回调机制

void processData(int* data, int size, void (*callback)(int)) {
    for (int i = 0; i < size; i++) {
        callback(data[i]);
    }
}

何时使用 std::function?

// ✅ 适合场景:
// 1. 需要存储不同类型的可调用对象
// 2. 需要lambda表达式
// 3. 需要函数对象(带状态)
// 4. 需要统一的接口
// 5. 性能要求不是极致

template<typename T>
void processData(const std::vector<T>& data, 
                 std::function<void(const T&)> callback) {
    for (const auto& item : data) {
        callback(item);
    }
}

// 可以传入各种类型:
// - 函数指针
// - Lambda表达式
// - 函数对象
// - 成员函数(通过bind)

总结对比表

特性C语言函数指针C++ std::function
存储内容函数地址可调用对象(可能是函数指针、函数对象、lambda等)
类型固定类型类型擦除,统一接口
可存储仅全局/静态函数函数指针、函数对象、lambda、成员函数等
性能最快(直接跳转)稍慢(虚函数调用)
内存开销最小(一个指针大小)较大(对象 + 动态分配)
类型安全编译时检查编译时检查
灵活性
C兼容性✅ 完全兼容❌ 不兼容

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐