UE4的垃圾回收
1 UObjects及子类
虚幻4引擎使用反射系统(机制)去实现垃圾回收。关于垃圾回收,你不用进行手动的去销毁你的UObjects类对象,你只需要保持对他们的引用。你的类需要继承UObject 类以支持垃圾回收。下面有个简单的例子。
UCLASS()
class MyGCType : public UObject
{
GENERATED_BODY()
};
在垃圾回收器里,这里有个概念被称为 root set (根集)。根集是一个基本的对象列表,回收器不会回收根集的对象。一个对象的引用路径如果在根集里,那么它将不会被回收。如果一个对象不存在这种引用路径,称之为“unreachable”(无法访问),并且在下次垃圾回收器运行时被回收(销毁)。引擎将在一定时间间隔运行垃圾回收。
1.1 UObjects类包含UObjects成员(UPROPERTY)
本节以AActors(继承自UObjects)的一个继承类AMyActor为例说明。
Actor通常不被垃圾回收。当你在关卡中生成Actor后,如果需要在关卡卸载前销毁Actor,你需要手动的调用Destroy() 方法去销毁,但它们不会被立刻的销毁,而是在下一个垃圾回收时期被销毁。
下面是一个常见的情况,你的Actor有UObject 类型的属性。
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY()
MyGCType* SafeObject;
MyGCType* DoomedObject;
AMyActor(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
SafeObject = NewObject<MyGCType>();
DoomedObject = NewObject<MyGCType>();
}
};
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
World->SpawnActor<AMyActor>(Location, Rotation);
}
当我们调用上面的函数时,我们在世界中生成一个Actor。Actor的构造函数创建2个对象。一个被标记了UPROPERTY,另一个使用普通的指针。由于Actor本来就是根集的一部分,SafeObject 将不会被垃圾回收,因为它在根集中可以被访问。然而,DoomedObject将不会那么幸运,我们没有对它标记UPROPERTY,所以回收器将不知道它的引用,所以事实上它会被销毁。
当一个UObject 被垃圾回收,所有的UPROPERTY 类型的引用都将变成空指针。你最好在使用时检查一下是否存在空指针。
if (MyActor->SafeObject != nullptr)
{
// Use SafeObject
}
正如我前面提到的,这非常的重要,Actor如果已经执行了Destroy() 方法,它将不会被移除,直到下次垃圾回收。你可以使用IsPendingKill() 方法去检查,这个UObject是否在被等待销毁。如果方法返回Ture,意味着这个UObject 已经无用了。
总结:1)UObjects对象本身已经加入回收机制;2)成员为UObjects对象时,需要UPROPERTY()以加入回收机制,否则不能加入回收机制。
1.2 UObjects类包含非UObjects成员
总结:1)UObjects对象本身已经加入回收机制;2)成员不为UObjects对象时,比如:基本数据类型(int、float)指针、结构体指针等,自行new/delete。
2 非UObject及子类
2.1 非UObjects类包含UObjects成员1
通常,非UObjects对象它能够添加一个对象引用而避免被垃圾回收。为了达到这种效果,你的类必须继承FGCObject ,并且重写AddReferencedObjects 。
class FMyNormalClass : public FGCObject
{
public:
UObject* SafeObject;
FMyNormalClass(UObject* Object)
: SafeObject(Object)
{
}
void AddReferencedObjects(FReferenceCollector& Collector) override
{
Collector.AddReferencedObject(SafeObject);
}
};
我们使用FReferenceCollector ,为我们所需要的UObject 对象,手动添加一个硬引用,使其不能被垃圾回收。当这个对象(FMyNormalClass)被销毁并且析构函数执行,该对象将会自动清除它所添加的引用。
总结:1)非UObjects对象借助UObjects成员且继承FGCObject,同时重写AddReferencedObjects,使得非UObjects对象与UObjects成员都加入回收机制。
2.2 非UObjects类包含UObjects成员2
如果在非UObjects类中有一个UObject* A变量,那么在创建完该变量之后,最好在非UObjects类的构造函数中,使用AddToRoot,这样该变量就不会被UE4自动GC。
UObject* A = NewObject();
A->AddToRoot();
然后在非UObjects类的析构函数中,使用RemoveFromRoot即可自动让UE4GC,防止内存泄漏。
总结:1)非UObjects对象包含的UObjects成员,通过AddToRoot和RemoveFromRoot,使得UObjects成员都加入回收机制;2)非UObjects对象,自行new/delete。
2.3 非UOjbects类包含非UObjects成员
总结:自行new/delete。
3 UStructs
UE4中提到:
“UStructs,正如前面提到,可以理解为轻量级的UObject。比如说,UStructs 不会被垃圾回收。如果你必须要用UStructs 类型的动态实例,你可能需要使用智能指针去代替,我们后面会提到。”
自己认为:
同“2 非UObject及子类”的说明。
4 容器类(TArray、TMap、TSet)
容器类一般作为类成员存在,以下按照其存储内容介绍。
4.1.当容器类存储了任意 UObjects指针
当容器类存储了任意 UObjects指针,也可被视为将UObjects对象加入垃圾回收机制,但是需要在以下两种情况中:1)当容器类作为UObjects类的成员时,需要加UPROPERTY;2)当容器类作为非UObjects类的成员时,需要非UObjects类继承FGCObject,并重写FGCObject::AddReferencedObjects()函数,与“2.1 非UObjects类包含UObjects成员1”不同的是,需要在FGCObject::AddReferencedObjects()内调用FReferenceCollector::AddReferencedObjects()函数(有多个对容器类的重载版本)。
4.2.当容器类存储了UObjects类外的指针
请自行new/delete。
4.3.UE4帮助文档摘抄
- 摘抄1
UPROPERTY 或UE4容器类(例如TArray)中存储的任意 UObject 指针都被视为垃圾回收的"引用"。首先让我们从简单示例入手。 - 摘抄2
TArray 添加了对其元素进行垃圾回收的好处。这样会假设 TArray 存储了 UObject 派生的指针。
UCLASS()
class UMyClass : UObject
{
GENERATED_BODY();
// ...
UPROPERTY()
AActor* GarbageCollectedActor;
UPROPERTY()
TArray<AActor*> GarbageCollectedArray;
TArray<AActor*> AnotherGarbageCollectedArray; //从这看,不加UPROPERTY也行
};