[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fhsafaVklH-vk81siZJzivn_9PlKqZVId5NiGnWuQku8":3,"$fJU-4tot_gC5fDkujNeoE-cGsdMy5V_KcdUXLuAnTFgw":16,"$flvWpNMqzZCfq2oTb0ZlMDouGrd6eVgoqg5h00JV79Y8":423},{"slug":4,"title":5,"description":6,"content":7,"content_html":8,"pub_date":9,"tags":10,"draft":15},"cpp-random-design-patterns","C++ 设计模式实战：RAII、观察者、工厂","用现代 C++（C++17\u002F20）实现三种高频设计模式：RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。","设计模式不是装饰——是经过验证的**问题解决方案**。\n\n这篇文章聚焦三种在 C++ 工程中最常见、最容易被误用的模式：RAII、观察者、工厂。每种模式都从一个具体的工程痛点出发，给出现代 C++ 的实现，并结合真实场景说明为什么这么做。\n\n---\n\n## RAII：资源获取即初始化\n\n### 问题场景\n\n你在写 HID 设备管理，需要打开设备句柄，做一些操作，最后关闭。最初的写法大概是这样：\n\n```cpp\n\u002F\u002F ❌ 危险写法：多个出口，每个都要记得 CloseHandle\nHANDLE hDevice = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, ...);\nif (hDevice == INVALID_HANDLE_VALUE) return false;\n\nif (!HidD_GetAttributes(hDevice, &attributes)) {\n    CloseHandle(hDevice);  \u002F\u002F 记得关\n    return false;\n}\n\n\u002F\u002F 如果这里抛异常呢？句柄泄漏！\nprocessDevice(hDevice);\n\nCloseHandle(hDevice);  \u002F\u002F 正常路径才会执行到\nreturn true;\n```\n\n函数有 3 个出口，每个都要手写 `CloseHandle`。一旦中间抛了异常，或者新同事加了一个 early return，句柄就泄漏了。\n\n### RAII 包装器实现\n\n解决方案是把资源的生命周期绑定到对象的生命周期——对象析构时资源自动释放：\n\n```cpp\n#include \u003Cwindows.h>\n#include \u003Cfunctional>\n#include \u003Cstdexcept>\n\n\u002F\u002F 通用 RAII 包装器：接受任意清理函数\nclass ScopeGuard {\npublic:\n    explicit ScopeGuard(std::function\u003Cvoid()> cleanup)\n        : cleanup_(std::move(cleanup)), active_(true) {}\n\n    ~ScopeGuard() {\n        if (active_) cleanup_();\n    }\n\n    \u002F\u002F 提前解除（不再清理，所有权转移时使用）\n    void dismiss() { active_ = false; }\n\n    \u002F\u002F 禁止拷贝\n    ScopeGuard(const ScopeGuard&) = delete;\n    ScopeGuard& operator=(const ScopeGuard&) = delete;\n\nprivate:\n    std::function\u003Cvoid()> cleanup_;\n    bool active_;\n};\n\n\u002F\u002F 专用 HID 设备句柄包装器\nclass HidDeviceHandle {\npublic:\n    explicit HidDeviceHandle(const wchar_t* devicePath) {\n        handle_ = CreateFileW(\n            devicePath,\n            GENERIC_READ | GENERIC_WRITE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE,\n            nullptr,\n            OPEN_EXISTING,\n            FILE_FLAG_OVERLAPPED,\n            nullptr\n        );\n        if (handle_ == INVALID_HANDLE_VALUE) {\n            throw std::runtime_error(\"Failed to open HID device\");\n        }\n    }\n\n    ~HidDeviceHandle() {\n        if (handle_ != INVALID_HANDLE_VALUE) {\n            CloseHandle(handle_);\n        }\n    }\n\n    \u002F\u002F 移动语义：所有权转移\n    HidDeviceHandle(HidDeviceHandle&& other) noexcept\n        : handle_(other.handle_) {\n        other.handle_ = INVALID_HANDLE_VALUE;\n    }\n\n    HidDeviceHandle& operator=(HidDeviceHandle&& other) noexcept {\n        if (this != &other) {\n            if (handle_ != INVALID_HANDLE_VALUE) CloseHandle(handle_);\n            handle_ = other.handle_;\n            other.handle_ = INVALID_HANDLE_VALUE;\n        }\n        return *this;\n    }\n\n    \u002F\u002F 禁止拷贝（句柄不能共享所有权）\n    HidDeviceHandle(const HidDeviceHandle&) = delete;\n    HidDeviceHandle& operator=(const HidDeviceHandle&) = delete;\n\n    HANDLE get() const { return handle_; }\n    bool valid() const { return handle_ != INVALID_HANDLE_VALUE; }\n\nprivate:\n    HANDLE handle_ = INVALID_HANDLE_VALUE;\n};\n```\n\n### 裸指针 vs unique_ptr vs 自定义 RAII\n\n```cpp\n\u002F\u002F ❌ 裸指针：手动管理，容易泄漏\nHANDLE h = OpenDevice();\n\u002F\u002F ... 中间任何异常或 return 都会泄漏\nCloseHandle(h);\n\n\u002F\u002F ✅ unique_ptr + 自定义 deleter：适合有 delete-like 清理函数的资源\nauto deleter = [](HANDLE* p) { if (*p != INVALID_HANDLE_VALUE) CloseHandle(*p); };\nstd::unique_ptr\u003CHANDLE, decltype(deleter)> hGuard(&h, deleter);\n\n\u002F\u002F ✅ 自定义 RAII 类：推荐方案，语义最清晰\n{\n    HidDeviceHandle device(L\"\\\\\\\\.\\\\HID#...#...#{...}\");\n    \u002F\u002F 任意操作，任意路径退出，句柄都会被 ~HidDeviceHandle() 关闭\n    HIDD_ATTRIBUTES attrs;\n    HidD_GetAttributes(device.get(), &attrs);\n}  \u002F\u002F 这里自动 CloseHandle\n```\n\n### 工程案例：多资源同时管理\n\n```cpp\nbool initializeDevice(const wchar_t* path) {\n    try {\n        HidDeviceHandle device(path);\n\n        \u002F\u002F 互斥锁也可以 RAII：std::lock_guard\n        std::mutex mtx;\n        std::lock_guard\u003Cstd::mutex> lock(mtx);\n\n        \u002F\u002F 临时缓冲区\n        auto buffer = std::make_unique\u003Cuint8_t[]>(1024);\n\n        \u002F\u002F 所有资源在 try 块结束时自动释放\n        \u002F\u002F 无需任何手动清理代码\n        return HidD_SetFeature(device.get(), buffer.get(), 1024);\n\n    } catch (const std::exception& e) {\n        \u002F\u002F device 析构已经关闭句柄，这里只需处理错误逻辑\n        logError(e.what());\n        return false;\n    }\n}\n```\n\n**RAII 的本质**：用 C++ 对象的确定性析构（deterministic destruction）替代手动资源管理。任何需要\"成对操作\"（open\u002Fclose、lock\u002Funlock、malloc\u002Ffree）的资源，都适合用 RAII 包装。\n\n---\n\n## 观察者模式：类型安全事件系统\n\n### 问题场景\n\n设备管理器需要在设备插拔时通知多个组件：UI 要刷新列表，日志模块要记录，业务层要更新状态。最简单（也最糟糕）的做法是在设备管理器里直接调用这些组件：\n\n```cpp\n\u002F\u002F ❌ 紧耦合：设备管理器知道太多东西\nclass DeviceManager {\n    void onDeviceArrival() {\n        ui_.refreshList();      \u002F\u002F 依赖 UI\n        logger_.logEvent(...);  \u002F\u002F 依赖 Logger\n        bizLayer_.update(...);  \u002F\u002F 依赖业务层\n    }\n};\n```\n\n添加新订阅者需要修改 `DeviceManager`，违反开闭原则，而且单元测试极其困难。\n\n### 基于 std::function 的类型安全事件系统\n\n```cpp\n#include \u003Cfunctional>\n#include \u003Cunordered_map>\n#include \u003Cvector>\n#include \u003Cstring>\n#include \u003Ctypeindex>\n#include \u003Cany>\n\n\u002F\u002F 事件基类（用于类型擦除）\nstruct EventBase {\n    virtual ~EventBase() = default;\n};\n\n\u002F\u002F 具体事件类型\nstruct DeviceArrivalEvent : EventBase {\n    std::string devicePath;\n    uint16_t vendorId;\n    uint16_t productId;\n};\n\nstruct DeviceRemovalEvent : EventBase {\n    std::string devicePath;\n};\n\nstruct DeviceDataEvent : EventBase {\n    std::string devicePath;\n    std::vector\u003Cuint8_t> data;\n};\n\n\u002F\u002F 类型安全事件总线\nclass EventBus {\npublic:\n    \u002F\u002F 订阅：返回 token，用于取消订阅\n    template\u003Ctypename EventT>\n    int subscribe(std::function\u003Cvoid(const EventT&)> handler) {\n        auto key = std::type_index(typeid(EventT));\n        int token = nextToken_++;\n        handlers_[key].push_back({\n            token,\n            [handler](const EventBase& e) {\n                handler(static_cast\u003Cconst EventT&>(e));\n            }\n        });\n        return token;\n    }\n\n    \u002F\u002F 取消订阅\n    template\u003Ctypename EventT>\n    void unsubscribe(int token) {\n        auto key = std::type_index(typeid(EventT));\n        auto it = handlers_.find(key);\n        if (it == handlers_.end()) return;\n        auto& vec = it->second;\n        vec.erase(\n            std::remove_if(vec.begin(), vec.end(),\n                [token](const auto& h) { return h.token == token; }),\n            vec.end()\n        );\n    }\n\n    \u002F\u002F 发布事件\n    template\u003Ctypename EventT>\n    void publish(const EventT& event) {\n        auto key = std::type_index(typeid(EventT));\n        auto it = handlers_.find(key);\n        if (it == handlers_.end()) return;\n        \u002F\u002F 拷贝一份，防止回调中修改 handlers_\n        auto handlers = it->second;\n        for (const auto& h : handlers) {\n            h.fn(event);\n        }\n    }\n\nprivate:\n    struct HandlerEntry {\n        int token;\n        std::function\u003Cvoid(const EventBase&)> fn;\n    };\n    std::unordered_map\u003Cstd::type_index, std::vector\u003CHandlerEntry>> handlers_;\n    int nextToken_ = 0;\n};\n\n\u002F\u002F 全局事件总线（实际项目中可用依赖注入）\ninline EventBus& getEventBus() {\n    static EventBus bus;\n    return bus;\n}\n```\n\n### 使用：设备插拔事件广播\n\n```cpp\n\u002F\u002F 设备管理器：只负责发布事件，不知道谁在监听\nclass DeviceManager {\npublic:\n    void onHotplugArrival(const std::string& path, uint16_t vid, uint16_t pid) {\n        \u002F\u002F 发布事件，其他组件自行处理\n        getEventBus().publish(DeviceArrivalEvent{path, vid, pid});\n    }\n\n    void onHotplugRemoval(const std::string& path) {\n        getEventBus().publish(DeviceRemovalEvent{path});\n    }\n};\n\n\u002F\u002F UI 组件：订阅感兴趣的事件\nclass DeviceListView {\npublic:\n    DeviceListView() {\n        arrivalToken_ = getEventBus().subscribe\u003CDeviceArrivalEvent>(\n            [this](const DeviceArrivalEvent& e) {\n                addDeviceItem(e.devicePath, e.vendorId, e.productId);\n            }\n        );\n        removalToken_ = getEventBus().subscribe\u003CDeviceRemovalEvent>(\n            [this](const DeviceRemovalEvent& e) {\n                removeDeviceItem(e.devicePath);\n            }\n        );\n    }\n\n    ~DeviceListView() {\n        \u002F\u002F 析构时取消订阅，防止悬空回调\n        getEventBus().unsubscribe\u003CDeviceArrivalEvent>(arrivalToken_);\n        getEventBus().unsubscribe\u003CDeviceRemovalEvent>(removalToken_);\n    }\n\nprivate:\n    int arrivalToken_;\n    int removalToken_;\n    void addDeviceItem(const std::string&, uint16_t, uint16_t) { \u002F* ... *\u002F }\n    void removeDeviceItem(const std::string&) { \u002F* ... *\u002F }\n};\n\n\u002F\u002F 日志模块：独立订阅，DeviceManager 完全不知道它的存在\nclass DeviceLogger {\npublic:\n    DeviceLogger() {\n        token_ = getEventBus().subscribe\u003CDeviceArrivalEvent>(\n            [](const DeviceArrivalEvent& e) {\n                printf(\"[LOG] Device arrived: %s (VID=%04X PID=%04X)\\n\",\n                    e.devicePath.c_str(), e.vendorId, e.productId);\n            }\n        );\n    }\n    ~DeviceLogger() {\n        getEventBus().unsubscribe\u003CDeviceArrivalEvent>(token_);\n    }\nprivate:\n    int token_;\n};\n```\n\n### 与 Qt Signal\u002FSlot 对比\n\n| 特性 | Qt Signal\u002FSlot | 本文事件总线 |\n|------|---------------|-------------|\n| 类型安全 | ✅（编译期） | ✅（模板） |\n| 跨线程 | ✅（内置队列连接） | ❌（需额外处理） |\n| 依赖 | Qt MOC 预处理器 | 纯标准库 |\n| 运行时开销 | 中等（元对象系统） | 低（std::function） |\n| 动态订阅\u002F取消 | ✅ | ✅ |\n| 适用场景 | Qt 应用 | 无 Qt 依赖的 C++ 项目 |\n\n**观察者模式的核心价值**：发布者和订阅者互不依赖，可以独立演化、独立测试。增加新的监听者无需修改任何现有代码。\n\n---\n\n## 工厂模式：注册表式插件架构\n\n### 问题场景\n\n你的应用需要处理多种协议（HID、USB Bulk、Serial、BLE），每种协议对应不同的处理器类。最直观的写法是 if-else：\n\n```cpp\n\u002F\u002F ❌ 每次新增协议都要修改这里\nstd::unique_ptr\u003CProtocolHandler> createHandler(const std::string& type) {\n    if (type == \"hid\")    return std::make_unique\u003CHidHandler>();\n    if (type == \"serial\") return std::make_unique\u003CSerialHandler>();\n    if (type == \"ble\")    return std::make_unique\u003CBleHandler>();\n    return nullptr;\n}\n```\n\n随着协议类型增多，这个函数会无限膨胀，而且每次改动都要触碰核心代码。\n\n### 注册表式工厂（自注册）\n\n让每个处理器类**自己注册**到工厂，工厂完全不知道具体类型：\n\n```cpp\n#include \u003Cmemory>\n#include \u003Cstring>\n#include \u003Cunordered_map>\n#include \u003Cfunctional>\n#include \u003Cstdexcept>\n\n\u002F\u002F 抽象基类\nclass ProtocolHandler {\npublic:\n    virtual ~ProtocolHandler() = default;\n    virtual bool open(const std::string& path) = 0;\n    virtual void close() = 0;\n    virtual std::vector\u003Cuint8_t> read() = 0;\n    virtual bool write(const std::vector\u003Cuint8_t>& data) = 0;\n    virtual std::string type() const = 0;\n};\n\n\u002F\u002F 工厂注册表\nclass HandlerFactory {\npublic:\n    using Creator = std::function\u003Cstd::unique_ptr\u003CProtocolHandler>()>;\n\n    static HandlerFactory& instance() {\n        static HandlerFactory factory;\n        return factory;\n    }\n\n    \u002F\u002F 注册创建函数\n    void registerHandler(const std::string& type, Creator creator) {\n        registry_[type] = std::move(creator);\n    }\n\n    \u002F\u002F 创建实例\n    std::unique_ptr\u003CProtocolHandler> create(const std::string& type) const {\n        auto it = registry_.find(type);\n        if (it == registry_.end()) {\n            throw std::runtime_error(\"Unknown handler type: \" + type);\n        }\n        return it->second();\n    }\n\n    \u002F\u002F 查询已注册类型\n    std::vector\u003Cstd::string> registeredTypes() const {\n        std::vector\u003Cstd::string> types;\n        for (const auto& [key, _] : registry_) types.push_back(key);\n        return types;\n    }\n\nprivate:\n    std::unordered_map\u003Cstd::string, Creator> registry_;\n};\n\n\u002F\u002F 自注册辅助类模板\ntemplate\u003Ctypename T>\nclass AutoRegister {\npublic:\n    explicit AutoRegister(const std::string& type) {\n        HandlerFactory::instance().registerHandler(\n            type,\n            []() -> std::unique_ptr\u003CProtocolHandler> {\n                return std::make_unique\u003CT>();\n            }\n        );\n    }\n};\n\n\u002F\u002F 自注册宏（简化样板代码）\n#define REGISTER_HANDLER(ClassName, TypeName) \\\n    static AutoRegister\u003CClassName> _autoReg_##ClassName{TypeName}\n```\n\n### 具体处理器实现（自注册）\n\n```cpp\n\u002F\u002F hid_handler.cpp\nclass HidHandler : public ProtocolHandler {\npublic:\n    bool open(const std::string& path) override {\n        handle_ = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,\n                              FILE_SHARE_READ | FILE_SHARE_WRITE,\n                              nullptr, OPEN_EXISTING, 0, nullptr);\n        return handle_ != INVALID_HANDLE_VALUE;\n    }\n\n    void close() override {\n        if (handle_ != INVALID_HANDLE_VALUE) {\n            CloseHandle(handle_);\n            handle_ = INVALID_HANDLE_VALUE;\n        }\n    }\n\n    std::vector\u003Cuint8_t> read() override {\n        std::vector\u003Cuint8_t> buf(65);\n        DWORD bytesRead = 0;\n        ReadFile(handle_, buf.data(), 65, &bytesRead, nullptr);\n        buf.resize(bytesRead);\n        return buf;\n    }\n\n    bool write(const std::vector\u003Cuint8_t>& data) override {\n        DWORD written = 0;\n        return WriteFile(handle_, data.data(), data.size(), &written, nullptr);\n    }\n\n    std::string type() const override { return \"hid\"; }\n\nprivate:\n    HANDLE handle_ = INVALID_HANDLE_VALUE;\n};\n\n\u002F\u002F 在 .cpp 文件全局作用域自动注册（程序启动时执行）\nREGISTER_HANDLER(HidHandler, \"hid\");\n\n\u002F\u002F serial_handler.cpp（同理，自包含，不修改工厂）\nclass SerialHandler : public ProtocolHandler {\n    \u002F\u002F ... 实现\n    std::string type() const override { return \"serial\"; }\n};\nREGISTER_HANDLER(SerialHandler, \"serial\");\n```\n\n### 使用工厂\n\n```cpp\n\u002F\u002F 调用方完全不依赖具体类型\nvoid connectToDevice(const std::string& type, const std::string& path) {\n    auto handler = HandlerFactory::instance().create(type);\n    if (!handler->open(path)) {\n        throw std::runtime_error(\"Failed to open device\");\n    }\n    \u002F\u002F handler 是 unique_ptr，RAII 保证 close\n}\n\n\u002F\u002F 动态加载配置（从 JSON\u002F命令行读取类型字符串）\nvoid loadDevicesFromConfig(const std::vector\u003CDeviceConfig>& configs) {\n    for (const auto& cfg : configs) {\n        try {\n            auto handler = HandlerFactory::instance().create(cfg.type);\n            handler->open(cfg.path);\n            \u002F\u002F ...\n        } catch (const std::exception& e) {\n            fprintf(stderr, \"Failed to create handler for %s: %s\\n\",\n                    cfg.type.c_str(), e.what());\n        }\n    }\n}\n```\n\n### C++17 if constexpr 与 std::variant 在工厂中的应用\n\n工厂模式结合 `std::variant` 可以在**编译期**消除运行时分支，适合类型集合固定的场景：\n\n```cpp\n#include \u003Cvariant>\n\n\u002F\u002F 已知类型集合（编译期固定）\nusing HandlerVariant = std::variant\u003CHidHandler, SerialHandler, BleHandler>;\n\n\u002F\u002F 根据枚举创建变体（编译期分派）\nenum class HandlerType { Hid, Serial, Ble };\n\nHandlerVariant createHandlerVariant(HandlerType type) {\n    switch (type) {\n        case HandlerType::Hid:    return HidHandler{};\n        case HandlerType::Serial: return SerialHandler{};\n        case HandlerType::Ble:    return BleHandler{};\n    }\n    std::unreachable();  \u002F\u002F C++23，C++17 用 __builtin_unreachable()\n}\n\n\u002F\u002F std::visit + if constexpr：针对不同类型执行不同逻辑\nvoid processHandler(HandlerVariant& handler) {\n    std::visit([](auto& h) {\n        using T = std::decay_t\u003Cdecltype(h)>;\n\n        if constexpr (std::is_same_v\u003CT, HidHandler>) {\n            \u002F\u002F HID 特有操作：设置报告描述符\n            h.setReportDescriptor();\n        } else if constexpr (std::is_same_v\u003CT, BleHandler>) {\n            \u002F\u002F BLE 特有操作：配对\n            h.startPairing();\n        } else {\n            \u002F\u002F 通用操作\n            h.open(\"\u002Fdev\u002FttyUSB0\");\n        }\n    }, handler);\n}\n```\n\n**variant 工厂 vs 注册表工厂的选择**：\n\n| 场景 | 推荐方案 |\n|------|---------|\n| 类型集合固定，编译期已知 | `std::variant` + `if constexpr` |\n| 类型集合动态扩展（插件系统） | 注册表工厂 |\n| 需要运行时字符串映射（配置文件） | 注册表工厂 |\n| 性能极敏感，无虚函数开销 | `std::variant` |\n\n---\n\n## 三种模式的协同\n\n这三种模式在实际项目中往往配合使用：\n\n```\n工厂模式\n  └─ 创建具体的 ProtocolHandler（RAII 包装底层句柄）\n       └─ 发布事件到 EventBus（观察者）\n            ├─ UI 订阅 → 刷新设备列表\n            ├─ Logger 订阅 → 记录日志\n            └─ Monitor 订阅 → 更新状态机\n```\n\n- **RAII** 确保资源不泄漏，不论执行路径如何\n- **观察者** 解耦生产者和消费者，两侧可独立变化\n- **工厂** 隔离创建逻辑，调用方不依赖具体类型\n\n这不是理论，是在 HID 设备管理、协议层抽象、UI 事件系统中被反复验证过的工程实践。","\u003Cp>设计模式不是装饰——是经过验证的\u003Cstrong>问题解决方案\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cp>这篇文章聚焦三种在 C++ 工程中最常见、最容易被误用的模式：RAII、观察者、工厂。每种模式都从一个具体的工程痛点出发，给出现代 C++ 的实现，并结合真实场景说明为什么这么做。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"raii-资源获取即初始化\">RAII：资源获取即初始化\u003C\u002Fh2>\n\u003Ch3 id=\"问题场景\">问题场景\u003C\u002Fh3>\n\u003Cp>你在写 HID 设备管理，需要打开设备句柄，做一些操作，最后关闭。最初的写法大概是这样：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 危险写法：多个出口，每个都要记得 CloseHandle\nHANDLE hDevice = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, ...);\nif (hDevice == INVALID_HANDLE_VALUE) return false;\n\nif (!HidD_GetAttributes(hDevice, &amp;attributes)) {\n    CloseHandle(hDevice);  \u002F\u002F 记得关\n    return false;\n}\n\n\u002F\u002F 如果这里抛异常呢？句柄泄漏！\nprocessDevice(hDevice);\n\nCloseHandle(hDevice);  \u002F\u002F 正常路径才会执行到\nreturn true;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>函数有 3 个出口，每个都要手写 \u003Ccode>CloseHandle\u003C\u002Fcode>。一旦中间抛了异常，或者新同事加了一个 early return，句柄就泄漏了。\u003C\u002Fp>\n\u003Ch3 id=\"raii-包装器实现\">RAII 包装器实现\u003C\u002Fh3>\n\u003Cp>解决方案是把资源的生命周期绑定到对象的生命周期——对象析构时资源自动释放：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;windows.h&gt;\n#include &lt;functional&gt;\n#include &lt;stdexcept&gt;\n\n\u002F\u002F 通用 RAII 包装器：接受任意清理函数\nclass ScopeGuard {\npublic:\n    explicit ScopeGuard(std::function&lt;void()&gt; cleanup)\n        : cleanup_(std::move(cleanup)), active_(true) {}\n\n    ~ScopeGuard() {\n        if (active_) cleanup_();\n    }\n\n    \u002F\u002F 提前解除（不再清理，所有权转移时使用）\n    void dismiss() { active_ = false; }\n\n    \u002F\u002F 禁止拷贝\n    ScopeGuard(const ScopeGuard&amp;) = delete;\n    ScopeGuard&amp; operator=(const ScopeGuard&amp;) = delete;\n\nprivate:\n    std::function&lt;void()&gt; cleanup_;\n    bool active_;\n};\n\n\u002F\u002F 专用 HID 设备句柄包装器\nclass HidDeviceHandle {\npublic:\n    explicit HidDeviceHandle(const wchar_t* devicePath) {\n        handle_ = CreateFileW(\n            devicePath,\n            GENERIC_READ | GENERIC_WRITE,\n            FILE_SHARE_READ | FILE_SHARE_WRITE,\n            nullptr,\n            OPEN_EXISTING,\n            FILE_FLAG_OVERLAPPED,\n            nullptr\n        );\n        if (handle_ == INVALID_HANDLE_VALUE) {\n            throw std::runtime_error(&quot;Failed to open HID device&quot;);\n        }\n    }\n\n    ~HidDeviceHandle() {\n        if (handle_ != INVALID_HANDLE_VALUE) {\n            CloseHandle(handle_);\n        }\n    }\n\n    \u002F\u002F 移动语义：所有权转移\n    HidDeviceHandle(HidDeviceHandle&amp;&amp; other) noexcept\n        : handle_(other.handle_) {\n        other.handle_ = INVALID_HANDLE_VALUE;\n    }\n\n    HidDeviceHandle&amp; operator=(HidDeviceHandle&amp;&amp; other) noexcept {\n        if (this != &amp;other) {\n            if (handle_ != INVALID_HANDLE_VALUE) CloseHandle(handle_);\n            handle_ = other.handle_;\n            other.handle_ = INVALID_HANDLE_VALUE;\n        }\n        return *this;\n    }\n\n    \u002F\u002F 禁止拷贝（句柄不能共享所有权）\n    HidDeviceHandle(const HidDeviceHandle&amp;) = delete;\n    HidDeviceHandle&amp; operator=(const HidDeviceHandle&amp;) = delete;\n\n    HANDLE get() const { return handle_; }\n    bool valid() const { return handle_ != INVALID_HANDLE_VALUE; }\n\nprivate:\n    HANDLE handle_ = INVALID_HANDLE_VALUE;\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"裸指针-vs-unique_ptr-vs-自定义-raii\">裸指针 vs unique_ptr vs 自定义 RAII\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 裸指针：手动管理，容易泄漏\nHANDLE h = OpenDevice();\n\u002F\u002F ... 中间任何异常或 return 都会泄漏\nCloseHandle(h);\n\n\u002F\u002F ✅ unique_ptr + 自定义 deleter：适合有 delete-like 清理函数的资源\nauto deleter = [](HANDLE* p) { if (*p != INVALID_HANDLE_VALUE) CloseHandle(*p); };\nstd::unique_ptr&lt;HANDLE, decltype(deleter)&gt; hGuard(&amp;h, deleter);\n\n\u002F\u002F ✅ 自定义 RAII 类：推荐方案，语义最清晰\n{\n    HidDeviceHandle device(L&quot;\\\\\\\\.\\\\HID#...#...#{...}&quot;);\n    \u002F\u002F 任意操作，任意路径退出，句柄都会被 ~HidDeviceHandle() 关闭\n    HIDD_ATTRIBUTES attrs;\n    HidD_GetAttributes(device.get(), &amp;attrs);\n}  \u002F\u002F 这里自动 CloseHandle\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"工程案例-多资源同时管理\">工程案例：多资源同时管理\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">bool initializeDevice(const wchar_t* path) {\n    try {\n        HidDeviceHandle device(path);\n\n        \u002F\u002F 互斥锁也可以 RAII：std::lock_guard\n        std::mutex mtx;\n        std::lock_guard&lt;std::mutex&gt; lock(mtx);\n\n        \u002F\u002F 临时缓冲区\n        auto buffer = std::make_unique&lt;uint8_t[]&gt;(1024);\n\n        \u002F\u002F 所有资源在 try 块结束时自动释放\n        \u002F\u002F 无需任何手动清理代码\n        return HidD_SetFeature(device.get(), buffer.get(), 1024);\n\n    } catch (const std::exception&amp; e) {\n        \u002F\u002F device 析构已经关闭句柄，这里只需处理错误逻辑\n        logError(e.what());\n        return false;\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>RAII 的本质\u003C\u002Fstrong>：用 C++ 对象的确定性析构（deterministic destruction）替代手动资源管理。任何需要&quot;成对操作&quot;（open\u002Fclose、lock\u002Funlock、malloc\u002Ffree）的资源，都适合用 RAII 包装。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"观察者模式-类型安全事件系统\">观察者模式：类型安全事件系统\u003C\u002Fh2>\n\u003Ch3 id=\"问题场景\">问题场景\u003C\u002Fh3>\n\u003Cp>设备管理器需要在设备插拔时通知多个组件：UI 要刷新列表，日志模块要记录，业务层要更新状态。最简单（也最糟糕）的做法是在设备管理器里直接调用这些组件：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 紧耦合：设备管理器知道太多东西\nclass DeviceManager {\n    void onDeviceArrival() {\n        ui_.refreshList();      \u002F\u002F 依赖 UI\n        logger_.logEvent(...);  \u002F\u002F 依赖 Logger\n        bizLayer_.update(...);  \u002F\u002F 依赖业务层\n    }\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>添加新订阅者需要修改 \u003Ccode>DeviceManager\u003C\u002Fcode>，违反开闭原则，而且单元测试极其困难。\u003C\u002Fp>\n\u003Ch3 id=\"基于-std-function-的类型安全事件系统\">基于 std::function 的类型安全事件系统\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;functional&gt;\n#include &lt;unordered_map&gt;\n#include &lt;vector&gt;\n#include &lt;string&gt;\n#include &lt;typeindex&gt;\n#include &lt;any&gt;\n\n\u002F\u002F 事件基类（用于类型擦除）\nstruct EventBase {\n    virtual ~EventBase() = default;\n};\n\n\u002F\u002F 具体事件类型\nstruct DeviceArrivalEvent : EventBase {\n    std::string devicePath;\n    uint16_t vendorId;\n    uint16_t productId;\n};\n\nstruct DeviceRemovalEvent : EventBase {\n    std::string devicePath;\n};\n\nstruct DeviceDataEvent : EventBase {\n    std::string devicePath;\n    std::vector&lt;uint8_t&gt; data;\n};\n\n\u002F\u002F 类型安全事件总线\nclass EventBus {\npublic:\n    \u002F\u002F 订阅：返回 token，用于取消订阅\n    template&lt;typename EventT&gt;\n    int subscribe(std::function&lt;void(const EventT&amp;)&gt; handler) {\n        auto key = std::type_index(typeid(EventT));\n        int token = nextToken_++;\n        handlers_[key].push_back({\n            token,\n            [handler](const EventBase&amp; e) {\n                handler(static_cast&lt;const EventT&amp;&gt;(e));\n            }\n        });\n        return token;\n    }\n\n    \u002F\u002F 取消订阅\n    template&lt;typename EventT&gt;\n    void unsubscribe(int token) {\n        auto key = std::type_index(typeid(EventT));\n        auto it = handlers_.find(key);\n        if (it == handlers_.end()) return;\n        auto&amp; vec = it-&gt;second;\n        vec.erase(\n            std::remove_if(vec.begin(), vec.end(),\n                [token](const auto&amp; h) { return h.token == token; }),\n            vec.end()\n        );\n    }\n\n    \u002F\u002F 发布事件\n    template&lt;typename EventT&gt;\n    void publish(const EventT&amp; event) {\n        auto key = std::type_index(typeid(EventT));\n        auto it = handlers_.find(key);\n        if (it == handlers_.end()) return;\n        \u002F\u002F 拷贝一份，防止回调中修改 handlers_\n        auto handlers = it-&gt;second;\n        for (const auto&amp; h : handlers) {\n            h.fn(event);\n        }\n    }\n\nprivate:\n    struct HandlerEntry {\n        int token;\n        std::function&lt;void(const EventBase&amp;)&gt; fn;\n    };\n    std::unordered_map&lt;std::type_index, std::vector&lt;HandlerEntry&gt;&gt; handlers_;\n    int nextToken_ = 0;\n};\n\n\u002F\u002F 全局事件总线（实际项目中可用依赖注入）\ninline EventBus&amp; getEventBus() {\n    static EventBus bus;\n    return bus;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"使用-设备插拔事件广播\">使用：设备插拔事件广播\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 设备管理器：只负责发布事件，不知道谁在监听\nclass DeviceManager {\npublic:\n    void onHotplugArrival(const std::string&amp; path, uint16_t vid, uint16_t pid) {\n        \u002F\u002F 发布事件，其他组件自行处理\n        getEventBus().publish(DeviceArrivalEvent{path, vid, pid});\n    }\n\n    void onHotplugRemoval(const std::string&amp; path) {\n        getEventBus().publish(DeviceRemovalEvent{path});\n    }\n};\n\n\u002F\u002F UI 组件：订阅感兴趣的事件\nclass DeviceListView {\npublic:\n    DeviceListView() {\n        arrivalToken_ = getEventBus().subscribe&lt;DeviceArrivalEvent&gt;(\n            [this](const DeviceArrivalEvent&amp; e) {\n                addDeviceItem(e.devicePath, e.vendorId, e.productId);\n            }\n        );\n        removalToken_ = getEventBus().subscribe&lt;DeviceRemovalEvent&gt;(\n            [this](const DeviceRemovalEvent&amp; e) {\n                removeDeviceItem(e.devicePath);\n            }\n        );\n    }\n\n    ~DeviceListView() {\n        \u002F\u002F 析构时取消订阅，防止悬空回调\n        getEventBus().unsubscribe&lt;DeviceArrivalEvent&gt;(arrivalToken_);\n        getEventBus().unsubscribe&lt;DeviceRemovalEvent&gt;(removalToken_);\n    }\n\nprivate:\n    int arrivalToken_;\n    int removalToken_;\n    void addDeviceItem(const std::string&amp;, uint16_t, uint16_t) { \u002F* ... *\u002F }\n    void removeDeviceItem(const std::string&amp;) { \u002F* ... *\u002F }\n};\n\n\u002F\u002F 日志模块：独立订阅，DeviceManager 完全不知道它的存在\nclass DeviceLogger {\npublic:\n    DeviceLogger() {\n        token_ = getEventBus().subscribe&lt;DeviceArrivalEvent&gt;(\n            [](const DeviceArrivalEvent&amp; e) {\n                printf(&quot;[LOG] Device arrived: %s (VID=%04X PID=%04X)\\n&quot;,\n                    e.devicePath.c_str(), e.vendorId, e.productId);\n            }\n        );\n    }\n    ~DeviceLogger() {\n        getEventBus().unsubscribe&lt;DeviceArrivalEvent&gt;(token_);\n    }\nprivate:\n    int token_;\n};\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"与-qt-signal-slot-对比\">与 Qt Signal\u002FSlot 对比\u003C\u002Fh3>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>特性\u003C\u002Fth>\n\u003Cth>Qt Signal\u002FSlot\u003C\u002Fth>\n\u003Cth>本文事件总线\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>类型安全\u003C\u002Ftd>\n\u003Ctd>✅（编译期）\u003C\u002Ftd>\n\u003Ctd>✅（模板）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>跨线程\u003C\u002Ftd>\n\u003Ctd>✅（内置队列连接）\u003C\u002Ftd>\n\u003Ctd>❌（需额外处理）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>依赖\u003C\u002Ftd>\n\u003Ctd>Qt MOC 预处理器\u003C\u002Ftd>\n\u003Ctd>纯标准库\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>运行时开销\u003C\u002Ftd>\n\u003Ctd>中等（元对象系统）\u003C\u002Ftd>\n\u003Ctd>低（std::function）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>动态订阅\u002F取消\u003C\u002Ftd>\n\u003Ctd>✅\u003C\u002Ftd>\n\u003Ctd>✅\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>适用场景\u003C\u002Ftd>\n\u003Ctd>Qt 应用\u003C\u002Ftd>\n\u003Ctd>无 Qt 依赖的 C++ 项目\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Cstrong>观察者模式的核心价值\u003C\u002Fstrong>：发布者和订阅者互不依赖，可以独立演化、独立测试。增加新的监听者无需修改任何现有代码。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2 id=\"工厂模式-注册表式插件架构\">工厂模式：注册表式插件架构\u003C\u002Fh2>\n\u003Ch3 id=\"问题场景\">问题场景\u003C\u002Fh3>\n\u003Cp>你的应用需要处理多种协议（HID、USB Bulk、Serial、BLE），每种协议对应不同的处理器类。最直观的写法是 if-else：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F ❌ 每次新增协议都要修改这里\nstd::unique_ptr&lt;ProtocolHandler&gt; createHandler(const std::string&amp; type) {\n    if (type == &quot;hid&quot;)    return std::make_unique&lt;HidHandler&gt;();\n    if (type == &quot;serial&quot;) return std::make_unique&lt;SerialHandler&gt;();\n    if (type == &quot;ble&quot;)    return std::make_unique&lt;BleHandler&gt;();\n    return nullptr;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>随着协议类型增多，这个函数会无限膨胀，而且每次改动都要触碰核心代码。\u003C\u002Fp>\n\u003Ch3 id=\"注册表式工厂-自注册\">注册表式工厂（自注册）\u003C\u002Fh3>\n\u003Cp>让每个处理器类\u003Cstrong>自己注册\u003C\u002Fstrong>到工厂，工厂完全不知道具体类型：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;memory&gt;\n#include &lt;string&gt;\n#include &lt;unordered_map&gt;\n#include &lt;functional&gt;\n#include &lt;stdexcept&gt;\n\n\u002F\u002F 抽象基类\nclass ProtocolHandler {\npublic:\n    virtual ~ProtocolHandler() = default;\n    virtual bool open(const std::string&amp; path) = 0;\n    virtual void close() = 0;\n    virtual std::vector&lt;uint8_t&gt; read() = 0;\n    virtual bool write(const std::vector&lt;uint8_t&gt;&amp; data) = 0;\n    virtual std::string type() const = 0;\n};\n\n\u002F\u002F 工厂注册表\nclass HandlerFactory {\npublic:\n    using Creator = std::function&lt;std::unique_ptr&lt;ProtocolHandler&gt;()&gt;;\n\n    static HandlerFactory&amp; instance() {\n        static HandlerFactory factory;\n        return factory;\n    }\n\n    \u002F\u002F 注册创建函数\n    void registerHandler(const std::string&amp; type, Creator creator) {\n        registry_[type] = std::move(creator);\n    }\n\n    \u002F\u002F 创建实例\n    std::unique_ptr&lt;ProtocolHandler&gt; create(const std::string&amp; type) const {\n        auto it = registry_.find(type);\n        if (it == registry_.end()) {\n            throw std::runtime_error(&quot;Unknown handler type: &quot; + type);\n        }\n        return it-&gt;second();\n    }\n\n    \u002F\u002F 查询已注册类型\n    std::vector&lt;std::string&gt; registeredTypes() const {\n        std::vector&lt;std::string&gt; types;\n        for (const auto&amp; [key, _] : registry_) types.push_back(key);\n        return types;\n    }\n\nprivate:\n    std::unordered_map&lt;std::string, Creator&gt; registry_;\n};\n\n\u002F\u002F 自注册辅助类模板\ntemplate&lt;typename T&gt;\nclass AutoRegister {\npublic:\n    explicit AutoRegister(const std::string&amp; type) {\n        HandlerFactory::instance().registerHandler(\n            type,\n            []() -&gt; std::unique_ptr&lt;ProtocolHandler&gt; {\n                return std::make_unique&lt;T&gt;();\n            }\n        );\n    }\n};\n\n\u002F\u002F 自注册宏（简化样板代码）\n#define REGISTER_HANDLER(ClassName, TypeName) \\\n    static AutoRegister&lt;ClassName&gt; _autoReg_##ClassName{TypeName}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"具体处理器实现-自注册\">具体处理器实现（自注册）\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F hid_handler.cpp\nclass HidHandler : public ProtocolHandler {\npublic:\n    bool open(const std::string&amp; path) override {\n        handle_ = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,\n                              FILE_SHARE_READ | FILE_SHARE_WRITE,\n                              nullptr, OPEN_EXISTING, 0, nullptr);\n        return handle_ != INVALID_HANDLE_VALUE;\n    }\n\n    void close() override {\n        if (handle_ != INVALID_HANDLE_VALUE) {\n            CloseHandle(handle_);\n            handle_ = INVALID_HANDLE_VALUE;\n        }\n    }\n\n    std::vector&lt;uint8_t&gt; read() override {\n        std::vector&lt;uint8_t&gt; buf(65);\n        DWORD bytesRead = 0;\n        ReadFile(handle_, buf.data(), 65, &amp;bytesRead, nullptr);\n        buf.resize(bytesRead);\n        return buf;\n    }\n\n    bool write(const std::vector&lt;uint8_t&gt;&amp; data) override {\n        DWORD written = 0;\n        return WriteFile(handle_, data.data(), data.size(), &amp;written, nullptr);\n    }\n\n    std::string type() const override { return &quot;hid&quot;; }\n\nprivate:\n    HANDLE handle_ = INVALID_HANDLE_VALUE;\n};\n\n\u002F\u002F 在 .cpp 文件全局作用域自动注册（程序启动时执行）\nREGISTER_HANDLER(HidHandler, &quot;hid&quot;);\n\n\u002F\u002F serial_handler.cpp（同理，自包含，不修改工厂）\nclass SerialHandler : public ProtocolHandler {\n    \u002F\u002F ... 实现\n    std::string type() const override { return &quot;serial&quot;; }\n};\nREGISTER_HANDLER(SerialHandler, &quot;serial&quot;);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"使用工厂\">使用工厂\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-cpp\">\u002F\u002F 调用方完全不依赖具体类型\nvoid connectToDevice(const std::string&amp; type, const std::string&amp; path) {\n    auto handler = HandlerFactory::instance().create(type);\n    if (!handler-&gt;open(path)) {\n        throw std::runtime_error(&quot;Failed to open device&quot;);\n    }\n    \u002F\u002F handler 是 unique_ptr，RAII 保证 close\n}\n\n\u002F\u002F 动态加载配置（从 JSON\u002F命令行读取类型字符串）\nvoid loadDevicesFromConfig(const std::vector&lt;DeviceConfig&gt;&amp; configs) {\n    for (const auto&amp; cfg : configs) {\n        try {\n            auto handler = HandlerFactory::instance().create(cfg.type);\n            handler-&gt;open(cfg.path);\n            \u002F\u002F ...\n        } catch (const std::exception&amp; e) {\n            fprintf(stderr, &quot;Failed to create handler for %s: %s\\n&quot;,\n                    cfg.type.c_str(), e.what());\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3 id=\"c-17-if-constexpr-与-std-variant-在工厂中的应用\">C++17 if constexpr 与 std::variant 在工厂中的应用\u003C\u002Fh3>\n\u003Cp>工厂模式结合 \u003Ccode>std::variant\u003C\u002Fcode> 可以在\u003Cstrong>编译期\u003C\u002Fstrong>消除运行时分支，适合类型集合固定的场景：\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-cpp\">#include &lt;variant&gt;\n\n\u002F\u002F 已知类型集合（编译期固定）\nusing HandlerVariant = std::variant&lt;HidHandler, SerialHandler, BleHandler&gt;;\n\n\u002F\u002F 根据枚举创建变体（编译期分派）\nenum class HandlerType { Hid, Serial, Ble };\n\nHandlerVariant createHandlerVariant(HandlerType type) {\n    switch (type) {\n        case HandlerType::Hid:    return HidHandler{};\n        case HandlerType::Serial: return SerialHandler{};\n        case HandlerType::Ble:    return BleHandler{};\n    }\n    std::unreachable();  \u002F\u002F C++23，C++17 用 __builtin_unreachable()\n}\n\n\u002F\u002F std::visit + if constexpr：针对不同类型执行不同逻辑\nvoid processHandler(HandlerVariant&amp; handler) {\n    std::visit([](auto&amp; h) {\n        using T = std::decay_t&lt;decltype(h)&gt;;\n\n        if constexpr (std::is_same_v&lt;T, HidHandler&gt;) {\n            \u002F\u002F HID 特有操作：设置报告描述符\n            h.setReportDescriptor();\n        } else if constexpr (std::is_same_v&lt;T, BleHandler&gt;) {\n            \u002F\u002F BLE 特有操作：配对\n            h.startPairing();\n        } else {\n            \u002F\u002F 通用操作\n            h.open(&quot;\u002Fdev\u002FttyUSB0&quot;);\n        }\n    }, handler);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>variant 工厂 vs 注册表工厂的选择\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>场景\u003C\u002Fth>\n\u003Cth>推荐方案\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>类型集合固定，编译期已知\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>std::variant\u003C\u002Fcode> + \u003Ccode>if constexpr\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>类型集合动态扩展（插件系统）\u003C\u002Ftd>\n\u003Ctd>注册表工厂\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>需要运行时字符串映射（配置文件）\u003C\u002Ftd>\n\u003Ctd>注册表工厂\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>性能极敏感，无虚函数开销\u003C\u002Ftd>\n\u003Ctd>\u003Ccode>std::variant\u003C\u002Fcode>\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Chr>\n\u003Ch2 id=\"三种模式的协同\">三种模式的协同\u003C\u002Fh2>\n\u003Cp>这三种模式在实际项目中往往配合使用：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>工厂模式\n  └─ 创建具体的 ProtocolHandler（RAII 包装底层句柄）\n       └─ 发布事件到 EventBus（观察者）\n            ├─ UI 订阅 → 刷新设备列表\n            ├─ Logger 订阅 → 记录日志\n            └─ Monitor 订阅 → 更新状态机\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cul>\n\u003Cli>\u003Cstrong>RAII\u003C\u002Fstrong> 确保资源不泄漏，不论执行路径如何\u003C\u002Fli>\n\u003Cli>\u003Cstrong>观察者\u003C\u002Fstrong> 解耦生产者和消费者，两侧可独立变化\u003C\u002Fli>\n\u003Cli>\u003Cstrong>工厂\u003C\u002Fstrong> 隔离创建逻辑，调用方不依赖具体类型\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>这不是理论，是在 HID 设备管理、协议层抽象、UI 事件系统中被反复验证过的工程实践。\u003C\u002Fp>\n","2026-05-01",[11,12,13,14],"cpp","设计模式","c++17","工程",false,[17,30,41,53,63,70,77,84,91,98,108,117,127,136,144,152,161,170,179,189,195,198,204,211,217,226,233,240,248,258,267,276,286,296,306,314,324,335,345,354,362,368,376,384,392,400,408,415],{"slug":18,"title":19,"description":20,"pub_date":21,"tags":22,"draft":15,"word_count":29},"ide-skills-guide","Agent Skills 完全指南：21 款第三方 Skill 深度评测与使用心得","全面评测 21 款第三方 Agent Skills，涵盖 Vue 生态、前端设计、构建工具、实用工具四大分类。从安装配置到实际使用场景，带你了解每个 Skill 的功能特点、最佳实践与使用心得。","2026-06-15",[23,24,25,26,27,28],"agent","skills","AI","效率工具","前端","Vue",4169,{"slug":31,"title":32,"description":33,"pub_date":34,"tags":35,"draft":15,"word_count":40},"linux-kernel-skeleton-struct-funcptr-container_of","Linux 内核骨架：struct、函数指针与 container_of","读懂 Linux 内核源码的三件套：巨大的 struct 组合代替继承、函数指针表实现虚派发、container_of 宏从嵌入成员找回完整对象。","2026-05-09",[36,37,38,39],"linux","kernel","C","container_of",1369,{"slug":42,"title":43,"description":44,"pub_date":45,"tags":46,"draft":15,"word_count":52},"astro-complete-guide-2025","Astro 5 深度剖析：Islands 架构原理、构建优化与 Cloudflare Workers 边缘部署","从编译器视角解析 Astro 5 的 Islands 架构实现原理，Content Layer API 的 Vite 插件机制，Server Islands 的流式渲染，以及如何在 Cloudflare Workers + D1 边缘环境下榨干性能。","2026-05-08",[47,48,49,50,51],"astro","frontend","cloudflare","performance","architecture",3663,{"slug":54,"title":55,"description":56,"pub_date":57,"tags":58,"draft":15,"word_count":62},"llm-prompt-engineering","Prompt Engineering 实战：让 LLM 真正听话的技巧","System prompt 怎么写、Few-shot 怎么设计、Chain-of-Thought 原理，以及常见失败模式和调试方法。","2026-05-03",[59,60,61],"ai","llm","工程实践",1723,{"slug":64,"title":65,"description":66,"pub_date":57,"tags":67,"draft":15,"word_count":69},"rag-system-design","RAG 系统设计：从 naive 到 production-ready","Retrieval-Augmented Generation 不只是「向量数据库 + LLM」，分块策略、召回质量、重排序、缓存才是工程核心。",[59,68,60,61],"rag",1613,{"slug":71,"title":72,"description":73,"pub_date":57,"tags":74,"draft":15,"word_count":76},"git-advanced-workflow","Git 进阶工作流：rebase、cherry-pick、bisect 的正确使用","merge 会了，但 rebase 总搞错？bisect 找 bug 提交？interactive rebase 整理历史？这篇一次说清楚。",[75,61],"git",1396,{"slug":78,"title":79,"description":80,"pub_date":57,"tags":81,"draft":15,"word_count":83},"docker-practical-guide","Docker 实战：从会用到用好","会 docker run 不够，Dockerfile 最佳实践、多阶段构建、Compose 编排、镜像瘦身才是日常真正需要的。",[82,36,61],"docker",1268,{"slug":85,"title":86,"description":87,"pub_date":57,"tags":88,"draft":15,"word_count":90},"anthropics-skills-guide","anthropics\u002Fskills：Anthropic 官方 Agent Skills 仓库解析","Anthropic 官方开源的 Agent Skills 标准仓库，127k stars，解析 SKILL.md 规范、17 个示例 skill 的设计模式，以及如何在 Claude Code \u002F Claude.ai \u002F API 中使用",[59,89,23,24],"Claude",2090,{"slug":92,"title":93,"description":94,"pub_date":57,"tags":95,"draft":15,"word_count":97},"karpathy-claude-code-guidelines","Karpathy 的 LLM 编码批评与 CLAUDE.md 最佳实践","基于 Andrej Karpathy 对 LLM 编程助手的观察，forrestchang 提炼出一个 CLAUDE.md 文件，4 条原则解决 AI 编码的典型失控问题：乱猜假设、过度设计、乱改代码、目标不清",[59,89,96,61],"Claude Code",2699,{"slug":99,"title":100,"description":101,"pub_date":57,"tags":102,"draft":15,"word_count":107},"typescript-advanced-patterns","TypeScript 高级模式：让类型系统为你工作","基础 TS 会了但类型总是 any？条件类型、映射类型、模板字面量类型、infer 关键字才是 TS 的真正威力。",[103,104,105,106],"typescript","类型系统","前端工程","高级模式",1419,{"slug":109,"title":110,"description":111,"pub_date":57,"tags":112,"draft":15,"word_count":116},"linux-performance-tuning","Linux 性能调优实战：从 top 到 perf 的完整工具链","遇到性能问题不知道从哪下手？这篇建立系统化的排查思路，从 CPU\u002F内存\u002FIO\u002F网络逐层分析。",[36,113,114,115],"性能","运维","系统编程",1524,{"slug":118,"title":119,"description":120,"pub_date":57,"tags":121,"draft":15,"word_count":126},"python-functional-programming","Python 函数式编程：map\u002Ffilter\u002Freduce 之外","Python 不是纯函数式语言，但 functools、itertools、偏函数、闭包这些工具用好了能让代码简洁一个量级。",[122,123,124,125],"python","函数式","闭包","装饰器",1867,{"slug":128,"title":129,"description":130,"pub_date":57,"tags":131,"draft":15,"word_count":135},"python-oop-guide","Python 面向对象：__init__ 之外你需要知道的","Python OOP 不只是 class + __init__，魔术方法、描述符、元类才是真正的武器。",[122,132,133,134],"OOP","面向对象","魔术方法",1792,{"slug":137,"title":138,"description":139,"pub_date":57,"tags":140,"draft":15,"word_count":143},"python-data-structures","Python 内置数据结构深度解析","list、dict、set、tuple 不只是数据容器，搞懂它们的底层实现和时间复杂度，才能写出高性能 Python。",[122,141,113,142],"数据结构","算法",1517,{"slug":145,"title":146,"description":147,"pub_date":57,"tags":148,"draft":15,"word_count":151},"python-basics-quick-start","Python 快速上手：写给有编程基础的人","已经会其他语言，想快速掌握 Python 的语法特性和思维方式，这篇是捷径。",[122,149,150],"入门","基础",1607,{"slug":153,"title":154,"description":155,"pub_date":57,"tags":156,"draft":15,"word_count":160},"python-dataclass-pydantic","Python dataclass vs Pydantic：数据类选型指南","dataclass 是标准库的轻量选择，Pydantic v2 是带验证的重武器，什么时候用哪个，这篇说清楚。",[122,157,158,159],"dataclass","pydantic","数据验证",1323,{"slug":162,"title":163,"description":164,"pub_date":57,"tags":165,"draft":15,"word_count":169},"python-asyncio-practical","Python asyncio 实战：从回调地狱到协程优雅","asyncio 是 Python 异步编程的核心，搞懂 event loop、Task、gather 这些概念才能写出真正高效的异步代码。",[122,166,167,168],"asyncio","并发","网络编程",1258,{"slug":171,"title":172,"description":173,"pub_date":57,"tags":174,"draft":15,"word_count":178},"python-type-hints-guide","Python 类型注解完全指南：从入门到实践","Python 3.5+ 引入类型注解，配合 mypy\u002Fpyright 让 Python 也能享受静态类型检查的好处。",[122,175,176,177],"typescript-style","type-hints","工具链",1102,{"slug":180,"title":181,"description":182,"pub_date":183,"tags":184,"draft":15,"word_count":188},"pwa-install-update-button","PWA 踩坑：为什么安装按钮从来不出现","从 beforeinstallprompt 到 Service Worker waiting，把 PWA 的安装与更新提示真正做对","2026-05-02",[185,186,187],"pwa","javascript","web",1683,{"slug":190,"title":191,"description":192,"pub_date":9,"tags":193,"draft":15,"word_count":194},"openclaw-vs-hermes-agent","OpenClaw vs Hermes Agent：两个本地优先 Agent 的设计差异","OpenClaw（Novita AI）和 Hermes Agent（Nous Research）都是本地运行的个人 AI Agent，但在记忆系统、技能学习、运行环境和模型生态上走了不同的路。深入对比两种架构的核心差异。",[59,23,60],1679,{"slug":4,"title":5,"description":6,"pub_date":9,"tags":196,"draft":15,"word_count":197},[11,12,13,14],2613,{"slug":199,"title":200,"description":201,"pub_date":9,"tags":202,"draft":15,"word_count":203},"data-structures-fundamentals","数据结构基础：从数组到红黑树","系统梳理常用数据结构的核心原理、时间复杂度和适用场景。数组、链表、栈、队列、哈希表、二叉树、堆、图，每种结构附实现要点和 C++ 代码片段。",[141,142,11,150],3004,{"slug":205,"title":206,"description":207,"pub_date":208,"tags":209,"draft":15,"word_count":210},"ai-agent-what-is","什么是 AI Agent？从 LLM 到自主执行","LLM 本身是无状态问答机，Agent 是什么让它’动’起来的？本文深入解析 Agent 的四个核心能力、ReAct 框架、工具调用原理，以及主流框架横向对比。","2026-04-30",[59,23,60],2116,{"slug":212,"title":213,"description":214,"pub_date":208,"tags":215,"draft":15,"word_count":216},"ai-agent-memory","AI Agent 的记忆系统：从上下文窗口到长期记忆","深入拆解 AI Agent 的四种记忆类型、上下文窗口压缩策略、RAG 向量检索原理，以及三种典型失败模式和工程选型建议。",[59,23,68],2052,{"slug":218,"title":219,"description":220,"pub_date":208,"tags":221,"draft":15,"word_count":225},"network-proxy-vpn-guide","代理与翻墙技术原理：从 HTTP 代理到现代协议","深入解析代理与 VPN 的本质区别，梳理从 SOCKS5 到 Shadowsocks、V2Ray\u002FXray、Hysteria2 的协议演进，以及机场订阅的技术本质。",[222,223,224],"网络","代理","协议",2148,{"slug":227,"title":228,"description":229,"pub_date":208,"tags":230,"draft":15,"word_count":151},"algorithm-binary-search","二分查找：永远写不对？记住这个模板","彻底搞清楚二分查找的边界问题：闭区间和左闭右开两套模板、三道经典 LeetCode 题目完整 C++ 实现，以及二分答案的进阶思路。",[142,231,232,11],"二分查找","leetcode",{"slug":234,"title":235,"description":236,"pub_date":208,"tags":237,"draft":15,"word_count":239},"algorithm-sliding-window","滑动窗口算法：从暴力到 O(n) 的思维跃迁","系统讲解滑动窗口算法的核心模板、适用题型，配合三道经典 LeetCode 题目的完整 C++ 实现，彻底理解双指针收缩思路。",[142,238,232,11],"滑动窗口",1943,{"slug":241,"title":242,"description":243,"pub_date":208,"tags":244,"draft":15,"word_count":247},"network-clash-config","Clash \u002F Mihomo 配置详解：规则、策略组与分流","深入解析 Clash\u002FMihomo 的核心配置结构，包括代理节点、策略组类型、规则优先级、DNS fake-ip 模式，以及一份实用的完整配置模板。",[222,245,223,246],"clash","配置",1292,{"slug":249,"title":250,"description":251,"pub_date":252,"tags":253,"draft":15,"word_count":257},"hid-hotplug","HID 设备热插拔检测：从 udev 到 node-hid","在 Linux 上用 node-hid + usb 库实现可靠的 USB HID 设备热插拔检测，踩坑记录","2026-04-28",[11,254,36,255,256],"hid","nodejs","electron",2039,{"slug":259,"title":260,"description":261,"pub_date":262,"tags":263,"draft":15,"word_count":266},"electron-ipc-types","Electron IPC 类型安全：从 any 到完全类型化","用 TypeScript 泛型封装 Electron IPC，彻底消灭 any，preload 契约集中管理","2026-04-25",[256,103,264,265],"ipc","vue",1446,{"slug":268,"title":269,"description":270,"pub_date":271,"tags":272,"draft":15,"word_count":275},"element-plus-popover-hide","手动关闭多个 el-popover（不用 v-model:visible）","通过 ref + Reflect.get 调用 hide() 方法手动关闭 Element Plus Popover，解释 Vue3 Proxy 导致无法直接调用实例方法的原因。","2024-10-25",[265,273,274],"element-plus","vue3",1321,{"slug":277,"title":278,"description":279,"pub_date":280,"tags":281,"draft":15,"word_count":285},"vite-vue3-ts-elementplus-pinia","用 Vite+（vp）从零搭建 Vue3 + TypeScript + Element Plus + Pinia + Vue Router","使用 Vite+ 统一工具链（vp）一条命令搭建 Vue3 全家桶，涵盖按需导入、Pinia store、路由配置，以及常见坑的解决方案。","2024-08-27",[265,282,103,273,283,284],"vite","pinia","vite-plus",1960,{"slug":287,"title":288,"description":289,"pub_date":290,"tags":291,"draft":15,"word_count":295},"cef-lnk2038-iterator-debug-level","CEF LNK2038：解决 _ITERATOR_DEBUG_LEVEL 不匹配错误","分析 CEF（Chromium Embedded Framework）集成时出现的 LNK2038 _ITERATOR_DEBUG_LEVEL 链接错误，从根本原因到解决方案的完整指南。","2024-05-07",[11,292,293,294],"CEF","Visual Studio","链接错误",1509,{"slug":297,"title":298,"description":299,"pub_date":300,"tags":301,"draft":15,"word_count":305},"npm-electron-install-fix","彻底解决 npm 安装 Electron 失败的问题","分析 npm install electron 失败的根本原因（下载二进制超时\u002F被墙），通过国内镜像（npmmirror）彻底解决，并介绍多种备选方案和常见错误排查。","2024-03-01",[256,302,303,304],"npm","前端工具链","国内镜像",1494,{"slug":307,"title":308,"description":309,"pub_date":310,"tags":311,"draft":15,"word_count":313},"git-out-of-memory","解决 git 报错：Fatal: Out of memory, malloc failed","分析 git 大仓库操作时出现 Out of memory malloc failed 的根本原因，通过调整 pack.windowMemory、http.postBuffer 和 git repack 彻底解决。","2024-01-31",[75,36,312],"工具",2244,{"slug":315,"title":316,"description":317,"pub_date":318,"tags":319,"draft":15,"word_count":323},"vmware-tools-install","在 VMware 虚拟机中安装 open-vm-tools 完整指南","详解 VMware Tools 的作用、open-vm-tools 与官方 VMware Tools 的区别，以及在 Ubuntu 虚拟机中安装并生效的完整步骤和常见问题排查。","2023-11-21",[320,36,321,322],"VMware","Ubuntu","虚拟机",2523,{"slug":325,"title":326,"description":327,"pub_date":328,"tags":329,"draft":15,"word_count":334},"load-balancing-algorithms","负载均衡算法完全指南：从轮询到一致性哈希","系统梳理静态与动态负载均衡算法，涵盖轮询、随机、权重、IP Hash、一致性 Hash、最少连接、最快响应等，并对比 Nginx、Dubbo、Spring Cloud LoadBalancer 的实现差异。","2023-11-15",[330,331,332,333],"分布式","负载均衡","Nginx","微服务",1764,{"slug":336,"title":337,"description":338,"pub_date":339,"tags":340,"draft":15,"word_count":344},"win-cw2a-ca2w","ATL 字符串转换：CW2A 与 CA2W 完全指南","详解 ATL 宏 CW2A\u002FCA2W 在 Unicode 与 ANSI 之间的字符串转换用法、头文件依赖、USES_CONVERSION 宏的作用与常见陷阱。","2023-06-09",[11,341,342,343],"windows","ATL","字符串",1665,{"slug":346,"title":347,"description":348,"pub_date":339,"tags":349,"draft":15,"word_count":353},"csharp-sendmessage-cpp","C# 通过 SendMessage 向 C++ 窗口发送消息与字符串","使用 P\u002FInvoke 调用 user32.dll 的 SendMessage，从 C# 发送自定义 WM_USER 消息及字符串指针给 C++ 原生窗口，并在 C++ 侧正确接收和转换。",[350,11,341,351,352],"C#","互操作","PInvoke",1554,{"slug":355,"title":356,"description":357,"pub_date":358,"tags":359,"draft":15,"word_count":361},"win-postmessage-vector","Windows PostMessage 跨线程传递 std::vector 指针","通过 PostMessage 在 Windows 消息队列中传递 std::vector 指针，使用 reinterpret_cast 将指针装入 LPARAM，并在接收方正确释放内存。","2023-05-26",[11,341,360],"WinAPI",1823,{"slug":363,"title":364,"description":365,"pub_date":358,"tags":366,"draft":15,"word_count":367},"exe-dll-single-package","将 EXE 和 DLL 打包成单一可执行文件","介绍两种将 exe 和依赖 dll 打包成单文件的方案：Enigma Virtual Box 和 WinRAR 自解压，适合发布 Windows 桌面程序时简化分发流程。",[341,11,312],1619,{"slug":369,"title":370,"description":371,"pub_date":358,"tags":372,"draft":15,"word_count":375},"cpp-random-mt19937","C++ 现代随机数生成：用 mt19937 彻底告别 rand()","深入讲解为什么 rand() 不够用，以及如何用 C++11 的 \u003Crandom> 库正确生成高质量随机数，涵盖 mt19937、各种分布和线程安全。",[11,373,374],"c++11","random",1549,{"slug":377,"title":378,"description":379,"pub_date":380,"tags":381,"draft":15,"word_count":383},"win-startup-registry","C++ 实现程序开机自启动：注册表方式详解","通过操作 Windows 注册表 Run 键实现程序开机自启动，包括 HKCU 与 HKLM 区别、完整封装代码、工作目录问题和 UAC 权限处理。","2022-12-26",[341,11,382],"registry",1201,{"slug":385,"title":386,"description":387,"pub_date":388,"tags":389,"draft":15,"word_count":391},"mfc-cstring-wparam","MFC 中 CString 与 WPARAM 之间的转换","详解 MFC 消息传递中 CString 无法直接强转为 WPARAM 的原因，以及两种正确的转换方案，并介绍结构体指针传递的正确姿势。","2022-11-25",[390,11,341],"mfc",1546,{"slug":393,"title":394,"description":395,"pub_date":396,"tags":397,"draft":15,"word_count":399},"duilib-static-build","正确编译 Duilib 静态库：避免 ATL 依赖和链接错误","详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库，解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。","2022-08-24",[11,398,341,390],"duilib",2639,{"slug":401,"title":402,"description":403,"pub_date":404,"tags":405,"draft":15,"word_count":407},"mfc-dpi-adaptive","MFC 界面自适应不同分辨率","MFC 对话框程序实现控件和字体随分辨率自动缩放的完整方案，附 DPI Awareness 配置说明","2022-08-17",[390,11,341,406],"dpi",1414,{"slug":409,"title":410,"description":411,"pub_date":412,"tags":413,"draft":15,"word_count":414},"mfc-drag-window","MFC 无标题栏窗口客户区拖动：三种方法对比","MFC 对话框去掉标题栏后如何实现拖动移动窗口，三种方案完整实现与适用场景分析","2022-08-16",[390,11,341],1633,{"slug":416,"title":417,"description":418,"pub_date":419,"tags":420,"draft":15,"word_count":422},"algorithm-number-complement","整数的补数：位运算掩码解法","LeetCode 476 题，用掩码 XOR 实现整数补数，附 C++\u002FPython\u002FJava 三种实现及补数与补码的区别","2021-03-08",[142,421,232],"位运算",1374,[]]