窄相检测必须用指针而非值传递,因值传递会引发大型对象(如AABB、Transform)的深拷贝,导致每帧数百次调用时堆分配与内存带宽压力剧增而拖垮性能。

窄相检测为什么必须用指针而不是值传递
物理引擎中窄相检测(narrow-phase collision detection)常需频繁访问刚体、碰撞体、变换矩阵等大型对象。若用值传递,每次调用 collide() 或 getClosestPoints() 都会触发深拷贝——AABB 结构体可能含 6 个 float,Transform 含 16 个 float 加旋转四元数,一次拷贝就是上百字节。在每帧调用数百次的场景下,堆分配+内存带宽压力直接拖垮性能。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有参与计算的几何体、运动状态、接触缓存对象,统一用
const T*或T*传参,禁止T值类型入参 - 避免临时构造:不要写
collide(AABB(a), AABB(b)),而应确保a和b是已存在的对象,传其地址 - 若需缓存中间结果(如 GJK 的 support point),用局部
static指针或预分配池管理,而非栈上反复 new/delete
如何安全复用指针避免悬空和误读
窄相检测常跨多帧维护接触点、穿透深度、法向量等状态。若指针指向生命周期短于检测周期的对象(比如一帧内创建又销毁的 CollisionPair),下帧访问就会读到垃圾内存,表现为随机穿透、抖动或崩溃。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有被指针引用的数据必须有明确所有权归属:推荐由
PhysicsWorld统一持有std::vector<:unique_ptr>></:unique_ptr>,检测函数只持Collider* - 禁用裸
new/delete;对临时几何数据(如凸包顶点缓存),用std::array<vec3></vec3>栈分配 + 指针偏移,而非new Vec3[verts] - 在调试构建中为关键指针加断言:
assert(ptr != nullptr && "Collider pointer invalidated before narrow phase");
GJK/EPA 中指针误用导致的数值不稳定
GJK 算法依赖支持函数 support(const Vec3& dir) 返回远点,EPA 则需维护单纯形顶点列表。若这些顶点存储为值类型并反复 push_back,内存不连续会导致 CPU 缓存失效;若用 std::vector<vec3></vec3> 但未 reserve,扩容重分配会让原有指针失效,后续迭代用到野指针,结果忽大忽小甚至 NaN。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 单纯形顶点统一存于固定大小数组(如
Vec3 m_simplex[4]),用索引而非指针遍历;仅当需动态扩展(EPA 边分裂)时,才用预分配池中的Vec3*指针 - 支持函数返回
const Vec3&而非Vec3,前提是该引用指向对象生命周期 ≥ 当前 GJK 迭代周期(通常来自 collider 的顶点缓存) - 禁用
std::vector::data()后长期持有指针;改用std::span<const vec3></const>封装,配合 lifetime-aware 断言
SIMD 对齐与指针强制转换的风险
为加速向量运算,常将 Vec3 设为 16 字节对齐,并用 _mm_load_ps 直接加载。但若指针来自非对齐内存(如普通 new char[] 分配、或 std::vector 默认分配器),强制转 __m128* 会触发 #GP 异常或静默错误结果。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 所有参与 SIMD 计算的结构体(
Vec3,AABB,Transform)必须显式声明对齐:struct alignas(16) Vec3 { ... }; - 容器分配需匹配:用
std::vector<vec3 aligned_allocator>></vec3>,或直接用std::aligned_storage_t手动管理内存池 - 避免
reinterpret_cast<__m128>(ptr)</__m128>;改用__m128 v = _mm_load_ps(reinterpret_cast<const float>(ptr));</const>,并确保ptr是float*且地址 % 16 == 0
最易被忽略的是:GJK 单纯形顶点缓存和 EPA 边列表一旦混用栈/堆/对齐/非对齐内存,错误不会立即暴露,而是在特定相对位置下才出现漏检或虚假接触——这类 bug 往往要靠帧回溯和内存访问日志才能定位。


















