C++11 新特性 万能函数容器之std::function
std::function是 C11 引入的一个非常强大的工具位于functional头文件中。简单来说你可以把它理解为一个**“万能函数容器”或“通用函数包装器”**。在 C11 之前如果我们想存储一个函数指针或者传递一个回调函数往往受到类型的严格限制比如函数指针无法直接存储 Lambda 表达式。std::function通过**类型擦除Type Erasure**技术统一了所有“可调用对象”的接口让它们可以像普通变量一样被赋值、存储和传递。 核心概念它能装什么只要函数签名返回值和参数列表匹配std::function可以装下以下所有东西普通函数Lambda 表达式尤其是带捕获的 Lambda这是它最大的用途之一仿函数函数对象即重载了operator()的类实例绑定后的成员函数通过std::bind 代码实战统一江湖看看下面这个例子std::function如何让不同类型的函数“殊途同归”#includeiostream#includefunctional// 必须包含的头文件#includevectorusingnamespacestd;// 1. 普通函数intadd(inta,intb){returnab;}// 2. 仿函数 (函数对象)structMultiplier{intoperator()(inta,intb){returna*b;}};intmain(){// 定义一个 std::function规定它必须接收两个 int返回一个 intstd::functionint(int,int)func;// --- 场景 1: 存储普通函数 ---funcadd;cout普通函数结果: func(3,4)endl;// 输出 7// --- 场景 2: 存储 Lambda 表达式 (带捕获) ---intfactor10;func[factor](inta,intb){return(ab)*factor;};coutLambda 结果: func(3,4)endl;// 输出 70// --- 场景 3: 存储仿函数 ---funcMultiplier();cout仿函数结果: func(3,4)endl;// 输出 12// --- 场景 4: 存入容器 (这是函数指针做不到的) ---vectorstd::functionint(int,int)vec;vec.push_back(add);// 放入普通函数vec.push_back([](inta,intb){returna-b;});// 放入 Lambda// vec.push_back(Multiplier()); // 放入仿函数cout容器调用: vec[1](10,5)endl;// 输出 5return0;} 对比std::functionvs 函数指针为什么有了函数指针还需要它看这张表就明白了特性函数指针 (int(*)(int, int))std::functionint(int, int)普通函数✅ 支持✅ 支持Lambda (无捕获)✅ 支持 (可隐式转换)✅ 支持Lambda (带捕获)❌不支持✅完美支持仿函数/对象❌ 不支持✅ 支持内存开销极小 (仅指针大小)稍大 (内部有多态实现的开销)调用速度极快稍慢 (可能有间接跳转)⚠️ 使用注意事项空状态检查std::function可以像指针一样为空。如果你调用了一个空的std::function程序会抛出std::bad_function_call异常。std::functionvoid()func;// 默认为空if(func){// 使用前最好检查func();}性能考量虽然std::function很方便但它不是零开销的。在极度追求性能的循环中比如每秒调用百万次直接使用模板或者函数指针可能会更快。但在绝大多数业务逻辑、回调处理中它的便利性远大于那一点点性能损耗。 C17 新特性类模板参数推导 (CTAD)从 C17 开始你甚至不需要显式写出签名编译器能自动推断automyFunc[](intx,inty){returnxy;};// C17 之前: std::functionint(int, int) f myFunc;// C17 及以后:std::function fmyFunc;// 编译器自动推导签名总结std::function是现代 C 实现回调机制、策略模式和事件驱动的基石。它让你不再受限于函数指针的僵硬语法可以更灵活地传递逻辑代码块。统一了所有“可调用对象”的接口让它们可以像普通变量一样被赋值、存储和传递。⚙️ 它是如何做到的这正是通过你提到的**类型擦除Type Erasure**技术实现的。std::function内部会创建一个抽象基类接口并为每一种被存储的可调用对象Lambda、仿函数等生成一个具体的派生类。这个派生类会实现调用接口。当你调用func()时std::function会通过内部的指针间接调用正确的对象而对外则统一表现为void(int)的函数签名。简单比喻std::function就像一个万能插座而各种函数、Lambda、仿函数就是不同规格的插头。类型擦除技术就是这个插座内部的“自适应”结构它让所有插头都能插进去并使用。