C++11 新特性 万能函数容器之std::function

std::function 是 C++11 引入的一个非常强大的工具,位于 <functional> 头文件中。

简单来说,你可以把它理解为一个**"万能函数容器""通用函数包装器"**。

在 C++11 之前,如果我们想存储一个函数指针,或者传递一个回调函数,往往受到类型的严格限制(比如函数指针无法直接存储 Lambda 表达式)。std::function 通过**类型擦除(Type Erasure)**技术,统一了所有"可调用对象"的接口,让它们可以像普通变量一样被赋值、存储和传递。

🎯 核心概念:它能装什么?

只要函数签名(返回值和参数列表)匹配,std::function 可以装下以下所有东西:

  1. 普通函数
  2. Lambda 表达式(尤其是带捕获的 Lambda,这是它最大的用途之一)
  3. 仿函数(函数对象) :即重载了 operator() 的类实例
  4. 绑定后的成员函数 (通过 std::bind

💻 代码实战:统一江湖

看看下面这个例子,std::function 如何让不同类型的函数"殊途同归":

cpp 复制代码
#include <iostream>
#include <functional> // 必须包含的头文件
#include <vector>

using namespace std;

// 1. 普通函数
int add(int a, int b) {
    return a + b;
}

// 2. 仿函数 (函数对象)
struct Multiplier {
    int operator()(int a, int b) {
        return a * b;
    }
};

int main() {
    // 定义一个 std::function,规定它必须接收两个 int,返回一个 int
    std::function<int(int, int)> func;

    // --- 场景 1: 存储普通函数 ---
    func = add;
    cout << "普通函数结果: " << func(3, 4) << endl; // 输出 7

    // --- 场景 2: 存储 Lambda 表达式 (带捕获) ---
    int factor = 10;
    func = [factor](int a, int b) { return (a + b) * factor; };
    cout << "Lambda 结果: " << func(3, 4) << endl; // 输出 70

    // --- 场景 3: 存储仿函数 ---
    func = Multiplier();
    cout << "仿函数结果: " << func(3, 4) << endl; // 输出 12

    // --- 场景 4: 存入容器 (这是函数指针做不到的) ---
    vector<std::function<int(int, int)>> vec;
    vec.push_back(add); // 放入普通函数
    vec.push_back([](int a, int b){ return a - b; }); // 放入 Lambda
    // vec.push_back(Multiplier()); // 放入仿函数
    
    cout << "容器调用: " << vec[1](10, 5) << endl; // 输出 5

    return 0;
}

🆚 对比:std::function vs 函数指针

为什么有了函数指针还需要它?看这张表就明白了:

特性 函数指针 (int(*)(int, int)) std::function<int(int, int)>
普通函数 ✅ 支持 ✅ 支持
Lambda (无捕获) ✅ 支持 (可隐式转换) ✅ 支持
Lambda (带捕获) 不支持 完美支持
仿函数/对象 ❌ 不支持 ✅ 支持
内存开销 极小 (仅指针大小) 稍大 (内部有多态实现的开销)
调用速度 极快 稍慢 (可能有间接跳转)

⚠️ 使用注意事项

  1. 空状态检查
    std::function 可以像指针一样为空。如果你调用了一个空的 std::function,程序会抛出 std::bad_function_call 异常。

    cpp 复制代码
    std::function<void()> func; // 默认为空
    if (func) { // 使用前最好检查
        func();
    }
  2. 性能考量
    虽然 std::function 很方便,但它不是零开销的。在极度追求性能的循环中(比如每秒调用百万次),直接使用模板或者函数指针可能会更快。但在绝大多数业务逻辑、回调处理中,它的便利性远大于那一点点性能损耗。

🚀 C++17 新特性:类模板参数推导 (CTAD)

从 C++17 开始,你甚至不需要显式写出签名,编译器能自动推断:

cpp 复制代码
auto myFunc = [](int x, int y) { return x + y; };

// C++17 之前: std::function<int(int, int)> f = myFunc;
// C++17 及以后:
std::function f = myFunc; // 编译器自动推导签名

总结:
std::function 是现代 C++ 实现回调机制策略模式事件驱动的基石。它让你不再受限于函数指针的僵硬语法,可以更灵活地传递逻辑代码块。

统一了所有"可调用对象"的接口,让它们可以像普通变量一样被赋值、存储和传递。

⚙️ 它是如何做到的?

这正是通过你提到的**类型擦除(Type Erasure)**技术实现的。

std::function 内部会创建一个抽象基类接口,并为每一种被存储的可调用对象(Lambda、仿函数等)生成一个具体的派生类。这个派生类会实现调用接口。当你调用 func() 时,std::function 会通过内部的指针间接调用正确的对象,而对外则统一表现为 void(int) 的函数签名。

简单比喻:std::function 就像一个万能插座 ,而各种函数、Lambda、仿函数就是不同规格的插头。类型擦除技术就是这个插座内部的"自适应"结构,它让所有插头都能插进去并使用。

相关推荐
小此方2 天前
Re:思考·重建·记录 现代C++ C++11篇(六) 从 shared_ptr 到 weak_ptr:起底智能指针的引用计数与循环引用之痛
开发语言·c++·c++11·现代c++
rockey6274 天前
基于AScript的python3脚本语言发布啦!
python·c#·.net·script·python3·eval·expression·function·动态脚本
Maiko Star8 天前
Function Calling:让大模型拥有“动手能力”
function·springai
小此方11 天前
Re:思考·重建·记录 现代C++ C++11篇 (四)C++ Lambda 全解析:编译器是如何为你生成仿函数的?
开发语言·c++·c++11·现代c++
结衣结衣.12 天前
手把手教你实现文档搜索引擎
linux·c++·搜索引擎·开源·c++11
2401_8920709815 天前
【C++11 后端实战】FixedThreadPool 固定线程池完整详解
c++11·生产者消费者·固定线程池
rockey62717 天前
AScript函数体系详解
c#·.net·script·eval·expression·function·动态脚本
老四啊laosi19 天前
[C++进阶] 25. C++11新特性(一)
c++·c++11·右值
量子炒饭大师20 天前
【C++11】RAII 义体加装指南 ——【包装器 与 异常】C++11中什么是包装器?有哪些包装器?C++常见异常有哪些?(附带完整代码讲解)
开发语言·c++·c++11·异常·包装器
xiaoye-duck20 天前
【C++:C++11】C++11新特性深度解析:从类新功能、Lambda表达式到包装器实战
开发语言·c++·c++11