原来 C++ 这么简单:每日十题轻松学 (Day 9 std::function 与回调系统设计篇)

📘 本篇围绕 std::function 为核心,深入探讨其在事件回调、接口抽象、函数封装等实战场景中的用法。结合前一天的函数对象与 Lambda 表达式知识,我们从底层原理出发,逐步构建一个轻量级的可插拔回调机制,帮助你理解类型擦除、参数绑定、函数对象封装的完整思路。


🔁 Day 8 回顾:函数对象与 Lambda 的本质与用法

知识点核心理解
函数对象定义 operator() 成员,封装状态 + 可调用行为
Lambda 表达式匿名可调用对象,支持变量捕获、传参、嵌套组合
混合用法函数对象负责状态逻辑,Lambda 编排任务流程
应用场景排序、过滤、延迟执行、注册回调、策略接口等

📌 回顾思考:如何将这些“可调用对象”在运行期灵活统一管理?如何封装成更易用的接口?答案正是 std::function


🎯 今日目标:掌握 std::function 的本质、优势与系统性用法

关键词学习目标
std::function理解其类型擦除原理、构造方式与使用技巧
std::bind / std::invoke掌握参数绑定与延迟调用的机制与实用场景
回调系统搭建一个轻量级事件注册与触发框架

在这里插入图片描述

✅ 一、std::function:统一所有“可调用对象”的万能容器

🔹 基本用法

#include <functional>
#include <iostream>

std::function<int(int, int)> add;

add = [](int a, int b) { return a + b; };
std::cout << add(3, 4);  // 输出 7

📌 能接受以下类型:

  • 普通函数指针
  • 成员函数(需绑定)
  • Lambda(含捕获)
  • 函数对象类实例

🔹 核心原理:类型擦除

  • C++ 中函数对象类型各异,不能统一
  • std::function 内部通过虚函数 + 堆分配实现“隐藏真实类型”
  • 用户只需关注“签名”,不必关心传入对象的具体类名

✅ 二、std::bind 与 std::invoke:封装 + 延迟调用工具

🔹 示例:bind 绑定参数

#include <functional>

int mul(int a, int b) { return a * b; }
auto times2 = std::bind(mul, 2, std::placeholders::_1);
std::cout << times2(10); // 输出 20

🔹 示例:invoke 统一调用形式

#include <functional>
#include <iostream>

void greet(const std::string& name) {
    std::cout << "Hello, " << name << "!\n";
}

std::invoke(greet, "Alice");  // 等价于 greet("Alice")

📌 典型应用:

  • 回调系统中传递已绑定对象函数
  • 封装多态回调统一入口调用

✅ 三、实战:设计一个轻量级回调系统

🧩 目标

  • 注册回调函数(任意可调用对象)
  • 多个回调可独立存在
  • 支持触发与取消绑定

🔸 Step 1:定义回调管理器

class EventManager {
public:
    using Callback = std::function<void()>;

    int addCallback(Callback cb) {
        callbacks[++currentId] = cb;
        return currentId;
    }

    void removeCallback(int id) {
        callbacks.erase(id);
    }

    void triggerAll() {
        for (auto& [id, cb] : callbacks) {
            cb();
        }
    }

private:
    std::map<int, Callback> callbacks;
    int currentId = 0;
};

🔸 Step 2:注册回调(函数 / lambda / 对象)

EventManager manager;

manager.addCallback([](){ std::cout << "Callback 1\n"; });
manager.addCallback([](){ std::cout << "Callback 2\n"; });

struct Printer {
    void operator()() const { std::cout << "Printer called\n"; }
};
manager.addCallback(Printer{});

manager.triggerAll();

📌 输出:

Callback 1
Callback 2
Printer called

✅ 四、进阶设计:支持带参数回调

🔹 改进支持:

class IntEventManager {
public:
    using Callback = std::function<void(int)>;

    int add(Callback cb) {
        callbacks[++id] = cb;
        return id;
    }

    void trigger(int value) {
        for (auto& [_, cb] : callbacks) {
            cb(value);
        }
    }

private:
    std::map<int, Callback> callbacks;
    int id = 0;
};

🔹 使用:

IntEventManager em;
em.add([](int v) { std::cout << "Value: " << v << std::endl; });
em.trigger(42);

📌 本质:std::function 允许你自由定义签名,只要签名匹配,即可任意组合封装函数对象。


✅ 五、实战应用场景总结

场景使用建议
注册点击按钮事件std::function<void()> + lambda
数据到达回调std::function<void(Data)> 或封装消息处理结构
组件通信std::function + bind 成员方法
插件机制提供 std::function 接口注册插件
批量调度/取消任务结合 id 存储,triggerAll + remove 实现调度控制

📚 总结与回顾

  • std::function 提供统一函数封装,支持多种可调用体
  • 类型擦除机制让它能隐藏 Lambda、函数对象的具体实现
  • std::bind 可提前绑定参数,std::invoke 可统一调用方式
  • 搭建回调系统的关键是:函数封装 + 映射管理 + 安全调用

🔭 Day 10 预告:智能指针 + 生命周期管理

下一期我们将结合回调机制,进入:

  • std::shared_ptr / std::weak_ptr
  • 回调引用对象生命周期的正确处理方式
  • 避免 dangling reference / callback leak 的模式设计

如果你希望加入异步调用、线程安全等更高级用法,也可以提前告诉我,我会将内容调整为你更关注的实战方向 💡

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值