《C++游戏编程入门(第四版)》:为何应避免用引用参数修改实参?
嘿,这个问题问得太戳人了——我刚啃C++游戏编程相关内容的时候,也觉得“直接改参数多方便啊,省得写返回值或者额外的变量”,直到踩了几次坑才明白书里说的道理。咱们结合游戏开发的场景来掰扯掰扯:
1. 可读性和维护性大打折扣
想象一下,你在写一个游戏的角色移动函数,比如void UpdatePosition(Character& player, float deltaTime)。如果这个函数暗地里修改了player的位置还好,但要是它顺便改了player的生命值或者状态?其他同事(甚至几个月后的你自己)看代码的时候,光看函数调用UpdatePosition(myPlayer, 0.016f),根本猜不到这个函数会偷偷改player的其他属性。
要是换成返回值或者专门的修改函数,比如Character UpdatePosition(const Character& player, float deltaTime)或者void AdjustPlayerPosition(Character& player, float deltaTime)(名字就明确说只改位置),可读性直接拉满,谁看都知道这函数干了啥。
2. 调试难度飙升
游戏里经常出现各种奇奇怪怪的bug,比如角色突然血量清空。如果你的代码里到处都是通过引用修改参数的函数,你得挨个排查所有调用过这个角色对象的函数——天知道哪个函数在背地里改了它的属性!
但如果函数都是纯函数(只依赖输入,不修改外部状态,返回新值),调试起来就简单多了:只要看输入输出是否符合预期,就能快速定位问题。
3. 并发和多线程隐患
现在的游戏大多是多线程的,比如渲染线程、逻辑线程分开跑。如果多个线程同时调用一个会修改共享变量的引用传参函数,很容易出现竞态条件——比如两个线程同时改角色的位置,结果位置计算混乱,角色瞬移或者卡Bug。
要是用不可变的输入+返回新对象的方式,就不用操心锁的问题,线程安全得多。
4. 容易引发意外的副作用
举个游戏里的例子:你写了一个float CalculateDamage(Enemy& enemy, int weaponPower),本来只想计算伤害值,结果不小心在函数里把enemy的防御值给改了。下次调用这个函数的时候,敌人的防御已经变了,导致伤害计算完全不符合预期,这种bug藏得特别深,很难发现。
当然,书里也说了“仅当需要修改实参变量的值时才传递引用”——不是完全不让用,而是要刻意控制:
- 尽量让函数的意图明确,比如函数名就告诉别人“我会改这个参数”,比如
void ResetPlayerState(Player& player) - 能返回值就返回值,尤其是简单类型(比如int、float),返回值比引用传参更直观
- 如果要修改多个值,可以考虑返回一个结构体或者类对象,而不是用多个引用参数
原书里的这句话其实是在引导你养成写健壮、可维护代码的习惯,毕竟游戏项目往往会越做越大,代码的可读性和可维护性比一时的方便重要得多。
内容的提问来源于stack exchange,提问作者Rafael

