避开这些‘天坑’!2025年投稿生信文章,我总结的选刊避雷指南(附具体期刊分析)
2026/4/21 5:24:57
在传统的 C++ 开发中,“谁申请,谁释放”是金科玉律。但在拥有数万个相互引用对象的游戏引擎中,手动管理引用链几乎是不可能的。如果 Actor A 引用了数据 B,而 B 又引用了特效 C,手动删除 A 时,你必须确保 B 和 C 没有被其他地方引用。
虚幻引擎通过一套名为Mark and Sweep(标记-清除)的垃圾回收机制,解决了这个难题。
UE 的 GC 并不是实时的(不像 C# 的分代回收),而是一种追踪式的回收。它分为两个主要阶段:
GC 会从一组被称为Root Set(根集)的对象开始,顺着它们身上的UPROPERTY指针向下摸索。
引擎遍历全局对象表(GUObjectArray),将所有没有“可达”标记的对象彻底从内存中抹除,并归还内存。
如果所有对象都被 GC,那谁来保护第一批对象不被回收?答案就是Root Set。
以下对象会自动进入根集,成为 GC 扫描的起点:
UGameEngine:引擎对象。UWorld:当前的关卡及其所有的 Actor。UGameInstance:贯穿游戏始终的全局对象。Root的对象:你可以通过AddToRoot()手动将一个对象固定在内存中(记得用RemoveFromRoot()释放,否则会内存泄漏)。UPROPERTY如此重要?这是初学者最容易崩溃的地方:GC 只认UPROPERTY。
UCLASS()classAMyCharacter:publicACharacter{GENERATED_BODY()// 情况 1:GC 知道 Character 引用了 WeaponUPROPERTY()UWeapon*MyWeapon;// 情况 2:GC 彻底无视这个指针!UWeapon*HiddenWeapon;};UPROPERTY指向MyWeapon。于是MyWeapon被标记为可达,幸免于难。HiddenWeapon,但 GC 扫描时看不见它。GC 会认为HiddenWeapon指向的对象没有人用,直接将其回收。此时HiddenWeapon就成了一个野指针,一旦访问立即崩溃。GC 扫描几万个对象是需要时间的。在早期版本中,GC 会“冻结”整个游戏线程来执行扫描,这就是为什么老游戏有时会突然卡一下。
UE 对 C++ 做的优化:
| 步骤 | 执行内容 | 开发者职责 |
|---|---|---|
| 1. 注册 | 对象创建时进入GUObjectArray | 使用NewObject或SpawnActor |
| 2. 追踪 | GC 扫描UPROPERTY引用链 | 务必给 UObject 指针加UPROPERTY() |
| 3. 标记 | 判定对象是否可达 | 正常引用即可,特殊情况使用AddToRoot |
| 4. 销毁 | 释放不可达对象的内存 | 确保没有非UPROPERTY指针指向它 |
TArray<UObject*>存对象,这个数组也必须加UPROPERTY(),否则数组里的对象会被当成垃圾清理掉。delete**:永远不要对UObject使用delete。如果你想让它消失,取消对它的引用,或者调用MarkAsGarbage()(UE5)。static UObject* MyGlobalPtr无法被UPROPERTY标记。如果必须使用全局 UObject,请考虑将其放入UGameInstance中管理。垃圾回收是虚幻引擎为 C++ 开发者提供的“安全气囊”。它通过UPROPERTY引用链建立了一套自动化的生存法则。只要你遵循“凡是 UObject 指针必加宏”的原则,你就已经掌握了 UE 内存管理的一半真谛。
下一篇预告:《11. 弱引用与软引用:TWeakObjectPtr 和 TSoftObjectPtr》。我们将探讨:如果我不希望一个指针“保住”对象的命,或者我希望延迟加载对象,该怎么办?