Hazel Engine Code Review对话总结

20 阅读4分钟

游戏引擎架构与现代C++工程实践笔记

(基于 Hazel Engine Code Review - Nathan Bags & The Cherno By Gemini)

这份笔记记录了“现代标准 C++ 规范 (RAII, Smart Pointers)”与“游戏引擎实战派 (Manual Memory, Intrusive Pointers)”在架构设计上的权衡与冲突,以及实用的工程经验。

1. 内存管理与生命周期 (Memory Management & Lifecycle)

• RAII vs. 显式生命周期控制

  • 核心冲突:标准 C++ 倾向于 RAII(资源获取即初始化,析构即释放);游戏引擎倾向于确定性控制。

  • 工程经验:

    • 对于引擎子系统(如 Renderer, Physics),推荐使用显式的 Initialize()Shutdown() 方法。

    • 原因:析构函数的隐式调用顺序在复杂依赖下(例如:必须在销毁 GPU Context 前销毁 Texture)极难控制和调试。显式调用能确保“启动”和“关闭”顺序严格对称。

• 二段式构造 (Two-Phase Initialization)

  • 原则:构造函数应轻量化,严禁在构造函数中执行可能失败的“重操作”(如分配显存、创建窗口句柄)。

  • 实践:采用 Constructor (设置参数) + Init() (实际资源分配) 的模式。

• 移动语义 (Move Semantics) 的资源陷阱

  • 风险:在实现移动赋值 (operator=) 时,直接用新资源覆盖 m_Data 指针。

  • 修正:必须先释放 this 对象当前持有的资源(deleteRelease),或者使用 std::swap 惯用法,否则会导致隐式内存泄漏。

2. 智能指针与资源所有权 (Smart Pointers & Ownership)

• 侵入式引用计数 (Intrusive RefCounting)

  • 实践:引擎通常实现自定义 Ref 而非使用 std::shared_ptr

  • 优势:

    1. 内存布局:对象与计数器在同一块内存,缓存友好。

    2. this 安全:天然支持从 this 指针创建安全引用(无需 enable_shared_from_this)。

    3. 多线程安全:配合渲染线程延迟释放资源。

• 性能优化:参数传递

  • 原则:如果不涉及所有权转移(Ownership Transfer),严禁按值传递智能指针 (void Func(Ref ptr))。

  • 修正:应使用 const Ref& 或原始指针,避免无谓的原子操作(Atomic Increment/Decrement)开销。

std::functionstd::unique_ptr 的冲突

  • 问题:std::function 要求捕获的对象可复制 (Copyable),导致无法捕获 unique_ptr

  • 方案:

    • (C++23) 使用 std::move_only_function

    • (Legacy) 被迫降级为 shared_ptr 或使用原始指针(需非常小心生命周期)。

3. API 设计与数据抽象 (API Design & Abstraction)

• 静态外观模式 (Static Facade)

  • 模式:如 Renderer::Begin(),内部调用单例。

  • 评价:虽破坏了纯面向对象设计,但极大简化了 Client API 的易用性,适合作为库的顶层入口。

• 内存块抽象:Buffer vs std::span

  • 趋势:现代 C++ 应使用 std::span (C++20) 来表达“一段不拥有所有权的连续内存”。它标准化了指针+长度的组合,且支持 Range-based for 循环。

• C API 互操作陷阱

  • 致命错误:将 std::string_view::data() 直接传给 C API (如 strlen, Windows API)。

  • 原因:string_view 不保证以 Null (\0) 结尾。

  • 修正:必须显式转换为 std::string 或手动确保边界。

4. 现代 C++ 陷阱与最佳实践 (Best Practices)

• Lambda 捕获 this

  • 规范:[=] 隐式捕获 this 在 C++20 已被弃用。

  • 风险:在异步/回调中,隐式捕获的 this 可能变成悬垂指针。

  • 修正:显式写出 [this][self = shared_from_this()]

• 类型转换 (Casting)

  • 原则:严禁使用 C 风格强转 (Type*)ptr

  • 理由:类型不安全且无法搜索。

  • 修正:必须使用 static_castreinterpret_cast。后者虽然危险,但它在代码审查时是“可搜索的 (Grep-able)”,能快速定位所有危险的内存操作。

• 枚举优化

  • 技巧:使用 using enum MyEnum; (C++20) 来简化 switch case 代码,减少冗余前缀。

5. 跨平台与并发细节

• 线程亲和性 (Thread Affinity)

  • 反模式:硬编码掩码(如 1 << 3)。应根据硬件并发数动态计算掩码。

• 宽字符转换

  • 反模式:直接强转 (wchar_t*)char_ptr

  • 修正:必须调用平台 API (如 MultiByteToWideChar) 进行真正的字符集转换 (UTF-8 -> UTF-16)。


总结建议: 在业务逻辑层遵守标准 C++ 规范(安全优先);在底层引擎核心可采用侵入式设计和手动管理(性能与控制优先),但必须清楚底层的代价。