c++11 ,function VS 函数指针
·
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;
}
};
关键点:
- 类型擦除:使用虚函数和多态隐藏具体类型
- 动态分配:通常需要在堆上分配内存存储可调用对象
- 间接调用:通过虚函数调用,有额外开销
性能对比
函数指针调用
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兼容性 | ✅ 完全兼容 | ❌ 不兼容 |
更多推荐



所有评论(0)