直接传对象会让碰撞检测变慢,因大型结构体按值传递会触发完整拷贝及构造/析构函数,每帧上千次调用可消耗15%+ CPU;而指针仅8字节、无拷贝开销,且编译器更易优化,配合合理内存布局(如热字段前置、避免跨缓存行)可显著提升性能。

为什么直接传对象会让碰撞检测变慢
物理引擎里频繁调用 collide(A, B) 时,如果 A 和 B 是大型结构体(比如含 3x3 矩阵、多个向量、包围盒缓存的 RigidBody),按值传递会触发完整拷贝——不只是数据复制,还可能触发构造/析构函数,尤其当内部含 std::vector 或 std::shared_ptr 时。实测在每帧上千次检测中,这能吃掉 15%+ 的 CPU 时间。
- 拷贝开销随对象大小线性增长,而指针始终是 8 字节(x64)
- 编译器对引用/指针参数更容易做寄存器优化,对值参则常保留栈帧
- 若函数内只读访问,
const RigidBody&比RigidBody*更安全;但需修改状态(如更新碰撞标志、累积冲量)时,裸指针更直白,避免const_cast陷阱
用指针替代值参的三个关键位置
不是所有地方都适合换指针,重点改高频、只读或需就地修改的接口:
- 碰撞形状相交测试:如
bool aabb_vs_aabb(const AABB<em> a, const AABB</em> b)——AABB小(通常 6 个 float),但每帧调用数万次,指针省下地址计算外的零开销 - 约束求解器中的接触点处理:如
apply_impulse(ContactPoint* cp, float dt)—— 必须修改cp->accumulated_impulse,传引用需额外取地址,指针更贴近硬件语义 - 宽度优先遍历中的物体索引:如
for (auto* body : broadphase_candidates) { narrowphase(body, query); }—— 避免在循环内反复构造临时对象
别碰这些:配置类(CollisionConfig)、一次性初始化参数(PhysicsWorld::init(const WorldSettings&)),它们调用频次低,且引用更易表达“不为空”的语义。
指针带来的空值风险怎么防
裸指针快,但 nullptr 解引用是硬崩溃。物理引擎里常见诱因:
立即学习“C++免费学习笔记(深入)”;
- 宽阶段剔除后未校验指针有效性(比如刚体被销毁但接触点数组还没清理)
- 多线程下,一个线程正删刚体,另一个线程还在用其指针跑碰撞
- 池化分配器回收内存后,指针未置空,变成悬垂指针
应对方式要务实:
- 所有外部传入的指针,在函数入口加
assert(a != nullptr && b != nullptr)(发布版可关,调试版必开) - 对池化对象,用带版本号的句柄(
BodyHandle)代替裸指针,查表转指针时顺便校验活跃状态 - 关键路径(如
integrate()、solve_constraints())里禁用裸指针,统一用gsl::not_null<RigidBody*>或自定义非空包装
性能差异真的只取决于指针吗
指针本身没魔法。真正影响性能的是内存布局和访问模式:
- 如果
RigidBody成员乱序排列(比如 float、ptr、vec3、int 交错),CPU 缓存行利用率低,即使用指针也卡顿 - 把常用字段(位置、速度、质量倒数)放在结构体开头,确保一次缓存行加载能覆盖 80% 的碰撞计算所需数据
- 避免跨 cache line 访问:x86-64 缓存行是 64 字节,
sizeof(RigidBody)超过 64 时,考虑拆分热/冷数据(如把调试用的std::string name移到单独结构体)
实测过一个案例:仅调整字段顺序 + 改用指针,narrowphase 函数在 Intel i7-11800H 上从 23ms/frame 降到 17ms/frame,比单纯换指针多榨出 3ms。
指针只是工具,它暴露的是你对内存和数据流的真实掌控程度。一上来就全换成 *,不如先 perf record -g 看看 cache-misses 在哪。


















