裸指针易致崩溃因无法安全处理高频碰撞下的对象生命周期,应改用weak_ptr跨帧安全访问或unique_ptr移动传递,避免共享引用计数开销与循环引用。

为什么用裸指针处理碰撞反馈反而容易崩溃
高频碰撞下直接用 new 分配的裸指针管理反馈对象,大概率触发野指针或重复释放——比如两个碰撞体同时触发回调,都试图通过 delete 同一个指针。现代 C++ 游戏逻辑中,裸指针不该持有所有权。
- 所有碰撞反馈对象应由
std::shared_ptr或对象池统一生命周期管理,指针仅作观察(std::weak_ptr或原始指针) - 避免在碰撞检测循环内做内存分配:把
std::make_shared<CollisionEvent>()移到预分配阶段,运行时只调用reset()复用 - 若用原始指针(如传给 ECS 系统),确保其指向的对象生命周期严格长于碰撞帧——常见做法是让反馈数据结构驻留在帧缓冲区(
FrameBuffer<CollisionEvent, 256>)里
如何用 std::weak_ptr 安全跨帧传递碰撞结果
碰撞检测通常在一帧前半段完成,而反馈逻辑(音效、粒子、伤害计算)可能在后半段执行,中间可能有对象销毁。此时用 std::shared_ptr 会阻止销毁,用裸指针又不安全——std::weak_ptr 是唯一合理选择。
- 检测函数返回
std::vector<std::weak_ptr<CollisionEvent>>,而非std::vector<std::shared_ptr<CollisionEvent>> - 消费端必须先调用
.lock():只有auto event = weak.lock(); if (event) { /* 安全使用 */ }才能访问 - 别在
lock()后长期缓存shared_ptr;高频场景下,一帧内多次lock()开销可忽略,但缓存会导致意外延长对象寿命
std::vector<CollisionEvent*> 在 ECS 架构中的实际用法
很多高性能游戏用 ECS(如 EnTT)时,会把碰撞事件作为临时组件写入世界,这时需要的是轻量、无引用计数的指针。但直接存裸指针风险高,必须配合明确的内存契约。
- 确保
CollisionEvent实例全部来自固定大小的栈数组或环形缓冲区(如std::array<CollisionEvent, 128>),地址永不变化 - ECS 系统只存储
CollisionEvent*,并在每帧开始前调用clear()重置缓冲区索引,不 delete 也不 new - 禁止将这类指针保存超过单帧:不能存进
std::map、不能作为成员变量、不能跨update()调用保留
避免 std::shared_ptr 引用计数成为性能瓶颈
每毫秒可能产生上千次碰撞,对每个事件做原子增减引用计数(std::shared_ptr 默认行为)会在多核 CPU 上引发 cache line bouncing,实测可能吃掉 5%+ 帧时间。
立即学习“C++免费学习笔记(深入)”;
- 启用无原子引用计数:用
std::shared_ptr<T, NoAtomicDeleter>自定义删除器(仅限单线程碰撞处理路径) - 更推荐方案:改用
entt::entity+entt::registry存储事件,用registry.ctx().emplace<CollisionEventBuffer>()统一管理,完全规避指针 - 若必须用智能指针,至少把
std::shared_ptr改成std::unique_ptr并用移动语义传递,避免共享——前提是反馈逻辑能接受“仅一次消费”语义
真正棘手的不是怎么拿指针,而是谁决定这个指针什么时候失效。高频碰撞里,90% 的 crash 都发生在“以为对象还活着”的那一帧。



















