导语
UE4 C++ 不同于原生的C++, UE内置了GC系统。对于有些人来说,GC帮我们处理了弃用的对象,防止了内存泄漏,确实是一件令人愉快的事情。然后有的时候,如果不了解GC规则,可能会给开发带来大麻烦。
什么对象会被GC 系统处理?
GC主要思想很简单——要让 GC系统 相信对象是多余的并删除它,必须满足以下几个条件:
这个对象不再被UE 的反射系统引用。
指向对象的指针没有被保存在容器中。
对象在其作用域内没有被强指针指向,比如shared pointers, shared references, unique pointers
创建对象的代码块经结束执行(离开作用域)
对象没有添加到根节点 AddToRoot()
对象必须是继承自Uobject
有一个要特殊注意的例外,我们可以显式地将UObject派生的类对象标记为销毁。
常见问题
- 场景中的 Actor 和 Actor 组件,被他们的父对象引用(比如 level 本身)。在场景中生成的actor,不需要特殊的GC 考虑
- 当一个新的关卡或者地图加载的时候,引擎将销毁world 并且创建一个新的world, 所有旧场景中的对象将被垃圾回收,包括GM GS,以及它们引用或指针指向的所有内容(假设这是指向对象的唯一引用或指针)。
- 并不是所有的不可用对象的指针都等于nullptr,因为如果一个对象被标记为销毁或者没有被正确初始化,那么它的指针可能不是nullptr。因此要养成用IsValid()来判断对象是否可用的习惯。
- 任何对象类如果不是继承自UObject 将不会被GC回收,也就是我们创建的对象类最好是UE 已经存在的类,至少也应该继承自 UObject.
GC对象树
到目前为止,我们已经讨论了UE引擎的GC规则和原理,那么它在技术上是如何实现的呢?
UE引擎维护了一张所有对象的树状图,在树的最根部节点是永远不会被回收的。
每当需要GC时,引擎将从根集合开始,并通过[反射系统]查看它们引用和指向的对象(https://www.unrealengine.com/en-US/blog/unreal-property-system-reflection)或容器类。任何指向或引用的内容都将添加到“不可触及”列表。然后,它将检查新添加的对象指向或引用的对象,并将所有这些对象也添加到树中。通过这种方式沿着树移动,垃圾收集系统最终会构建一个所有不可接触对象的列表,并删除所有其他对象。
如果一个对象可以通过引擎的属性系统指针追溯到根集,它将不会被垃圾收集。一旦这些与根集的联系被切断,对象将被GC掉。
Slate UI
需要注意的是Slate UI 是不太适用之前的GC 规则,从4.25开始,它不使用强指针来保持用户界面中使用的对象(通常是uasset)。这意味着如果一个对象被Slate UI引用,但是不再被其他对象引用,那么GC将无情地删除它。而且,当它使用的对象被GC时,Slate UI可能会使程序崩溃。
为了处理这一问题,请确保传递给Slate UI的每个对象都已受到严格保护,不受垃圾收集的影响。如有必要,可以使用显式强指针,但通常这将通过属性系统完成。例如,可以在垃圾收集图的根集中创建一个新对象,并让它保存与Slate UI小部件相对应的其他对象数组。这些对象将指向Slate UI使用的资源对象。每个专用于自己的Slate UI小部件的对象都可能在小部件启动后被销毁。
调试
有时我们的代码中有一个问题可能是由垃圾收集引起的,但我们还不能确定。这很可能是一个对象初始化问题,也可能是一系列其他问题。
我们可以使用断言在代码执行的任何阶段测试对象是否有效:
1 |
|
### 正确示例
第一个很好的习惯用法是在容器类(如TArray)中使用或者用UPROPERTY()声明指针。
一旦不再有“父”对象以这种方式指向“子”对象,“子”对象才能销毁。
1 | // 头文件中的声明 |
我们可以自由地从多个其他对象指向同一对象。只要至少有一个活动对象使用属性系统指向它,它就不会被垃圾收集。
“临时对象”会在作用域结束执行后进行垃圾收集。
下面是一段功能代码,它临时创建一个新对象,以从场景捕获组件捕获图片并将其存储到像素颜色数组中。
此范围结束后,TextureEnderTarget 本身将被GC适当地清除。
1 |
|
错误示例
两个坏习惯:
1、是将所有对象都 AddToRoot()
2、创建不符合上述GC规则的对象,但是又没有被及时的删除
有时在函数定义的范围内使用新指针创建对象,并将其指针返回(返回临时对象了)。有时,甚至大多数时候,它可能会起作用。
还有一个个坏习惯——过度依赖nullptr:
1 | **if**(ObjectPointer) { |
看起来非常方便,但即使对象未初始化或标记为kill,ObjectPointer的计算结果也将为true。如果要使用对象,最好使用 IsValid(ObjectPointer)来判断。
### 总结
总而言之,GC 系统是UE引擎使得代码强健的一部分,有效避免了内存泄漏。