不能直接用 raw 指针做场景剔除,因其导致生命周期失控、所有权模糊和缓存不友好;应改用 weak_ptr 存储、shared_ptr 管理所有权,并配合连续内存布局(如对象池+soa)提升性能。

为什么不能直接用 raw 指针做场景剔除管理
大型游戏场景中,物体数量动辄上万,剔除逻辑(如视锥剔除、遮挡剔除)必须高频调用且低延迟。用裸指针 GameObject* 直接管理,会立刻暴露三个硬伤:生命周期失控(对象提前析构但指针仍被剔除系统访问)、所有权模糊(多个系统同时持有一个 Entity* 导致 double-delete)、缓存不友好(指针随机跳转,CPU 预取失效)。这不是“能不能跑”的问题,而是“帧率掉到 30 帧以下还伴随偶发崩溃”的问题。
std::shared_ptr 和 std::weak_ptr 的分工必须明确
剔除系统本身不该持有对象所有权——它只读、不增删、不延长生命周期。所以所有参与剔除的实体,应该用 std::weak_ptr<gameobject></gameobject> 存储;而真正拥有生命周期控制权的(比如场景管理器、资源池),才用 std::shared_ptr<gameobject></gameobject>。
- 剔除循环里先调用
lock():只有返回非空std::shared_ptr才参与计算,避免访问已销毁对象 - 不要在剔除函数内长期 hold 住
lock()返回的shared_ptr——它会意外延长对象生命,干扰内存回收节奏 - 如果剔除后要触发渲染或物理更新,再通过
lock()获取一次强引用,而不是复用之前缓存的指针
剔除容器选 std::vector 而不是 std::list 或 std::unordered_set
剔除结果是临时的、每帧重建的,不是持久索引结构。用 std::vector<:weak_ptr>></:weak_ptr> 是更优选择:
-
std::vector连续内存 + 线性遍历,CPU 缓存命中率高,比链表跳指针快 3–5 倍(实测 10k 对象剔除耗时从 0.8ms 降到 0.2ms) -
std::unordered_set插入/查找虽平均 O(1),但哈希冲突和 rehash 会引发不可预测的卡顿,不适合帧时间敏感路径 - 剔除后需要排序(如按距离排序渲染)?
std::vector支持std::sort+ 自定义比较器,且能用std::span避免拷贝
手动控制内存布局比依赖智能指针更重要
指针只是间接层,真正卡性能的是数据布局。即使用了 std::weak_ptr,如果每个 GameObject 是独立 new 出来的,剔除循环仍然在满内存乱跳。
- 把空间相关对象(如同一区块的 NPC、建筑)分配在连续内存块里,用对象池(
std::vector<gameobject></gameobject>+ free-list)管理 - 剔除系统只存索引(
size_t)或指向池内元素的weak_ptr,而非跨堆指针 - 考虑用 SoA(Structure of Arrays)拆分剔除相关字段(如
std::vector<:vec3> positions;</:vec3>),让视锥测试只加载位置数据,减少 cache line 浪费
指针管理只是安全网,真正的性能瓶颈从来不在“怎么管”,而在“数据放哪”和“怎么访”。漏掉这一层,再漂亮的 shared_ptr 封装也救不回 60fps。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











