UE4实验使用 FGCObject 引用UObject

FGCObject

UE4有一套自己的GC机制,我觉得这篇文章讲的不错:《虚幻4垃圾回收剖析 - 风恋残雪 - 博客园》

概括来说,就是当一个对象“不再被需要”(如果把引用关系看作是一个“图”,那么就相当于这个对象“不可达”了)时,这个对象就要被清理掉。而为了表示这个对象是“被需要”的,就要让这个对象被引用

引用一个对象有多种途径,最常见的就是使用UPROPERTY宏来表示【对象】被另一个【引用者】UObject所引用,但这对于非UObject的【引用者】就没办法使用了。也可以使用AddToRoot表示【这个对象】处于一个引用关系图的“根”位置,但这样就不能表示它被【另一个对象】所“引用”,当【另一个对象】被销毁时,【这个对象】也不会被清理,因为它处于“根”位置,和【另一个对象】无关。

FGCObject则为非UObject【引用者】提供了一个引用UObject【对象】的功能,它是一个纯虚的基类,要求子类去实现AddReferencedObjects()方法。

/**
 * This class provides common registration for garbage collection for
 * non-UObject classes. It is an abstract base class requiring you to implement
 * the AddReferencedObjects() method.
 */
class COREUOBJECT_API FGCObject
/**
 * Pure virtual that must be overloaded by the inheriting class. Use this
 * method to serialize any UObjects contained that you wish to keep around.
 *
 * @param Collector The collector of referenced objects.
 */
virtual void AddReferencedObjects( FReferenceCollector& Collector ) = 0;

本篇的目标是构造一个小实验来测试FGCObject的效果。另外也对我感兴趣的一个问题进行探究:

  • AddReferencedObjects何时被调用?我关心它是因为,如果它是在构造函数之后立马调用,那么我在构造函数之后创建UObject时,那么新创建的对象就不会被引用了。会发生这种问题吗?

准备实验

1. 创建插件

使用编辑器模式为模板创建一个插件。使用此模板是因为它默认在左侧的“模式”选项卡中生成一个界面,方便放一些按钮。
在这里插入图片描述
插件名字TestGCPlg

2. 创建一个UObject类型用于测试

TestObject.h

#pragma once

#include "UObject/Object.h"

#include "TestObject.generated.h"

UCLASS()
class UTestObject : public UObject
{
	GENERATED_UCLASS_BODY()
};

TestObject.cpp

#include"TestObject.h"

UTestObject::UTestObject(const FObjectInitializer & ObjectInitializer)
	: Super(ObjectInitializer)
{}

他没有任何内容,只是一个空的,用来测试用的。之所以不直接用UObject类来测试是因为:

3. 创建一个测试的非UObject类,想要引用一个UObject

TestClass.h

#pragma once

#include"CoreMinimal.h"

class FTestClass
{
public:

	FTestClass();

	//想要引用的对象
	class UTestObject* MyObject;
};

TestClass.cpp

#include"TestClass.h"

#include"TestObject.h"

FTestClass::FTestClass()
{
	//创建一个新的UObject
	MyObject = NewObject<UTestObject>(GetTransientPackage(),TEXT("TestGCPlgTestObject"));
}

其中NewObject函数创建一个新的UObject,第一个参数是Outer。然而我的FTestClass并不是UObject,因此并不能将自己作为Outer,所以我用GetTransientPackage()来得到一个临时的包。

4. 测试用的操作

首先,在FTestGCPlgEdModeToolkit中声明两个成员:

//FTestClass的指针(记录它是为了方便操作)
class FTestClass* TestClassPtr;
//UObject的弱指针(用来查询UObject有没有被清理)
TWeakObjectPtr<UObject> ObjectPtr;

随后,我需要四种操作来进行测试:

1)创建FTestClass

TestClassPtr = new FTestClass();
ObjectPtr = TestClassPtr->MyObject;

创建一个新的FTestClass,同时把ObjectPtr弱指针指向它的对象。

2)销毁FTestClass

delete TestClassPtr;

3)执行一次GC

CollectGarbage(EObjectFlags::RF_NoFlags);

参数是RF_NoFlags,意味着不会排除任何Flag。
4)查询UObject是否被销毁了

if(ObjectPtr.IsValid())
	FMessageDialog::Open(EAppMsgType::Type::Ok, FText::FromString("Is Valid"));
else
	FMessageDialog::Open(EAppMsgType::Type::Ok, FText::FromString("Not Valid"));

通过弱指针TWeakObjectPtr的方法IsValid()来检测物体是否已经被销毁了。


完整代码如下:

void FTestGCPlgEdModeToolkit::Init(const TSharedPtr<IToolkitHost>& InitToolkitHost)
{
	SAssignNew(ToolkitWidget, SBorder)
	[
		SNew(SVerticalBox)
		+SVerticalBox::Slot().AutoHeight()
		[
			SNew(SButton).Text(FText::FromString("Create new TestClass"))
			.OnClicked_Lambda([this]()
				{
					TestClassPtr = new FTestClass();
					ObjectPtr = TestClassPtr->MyObject;
					return FReply::Handled();
				})
		]
		+SVerticalBox::Slot().AutoHeight()
		[
			SNew(SButton).Text(FText::FromString("Delete TestClass"))
			.OnClicked_Lambda([this]()
				{
					delete TestClassPtr;
					return FReply::Handled();
				})
		]
		+ SVerticalBox::Slot().AutoHeight()
		[
			SNew(SButton).Text(FText::FromString("Execute GC"))
			.OnClicked_Lambda([]()
				{
					CollectGarbage(EObjectFlags::RF_NoFlags);
					return FReply::Handled();
				})
		]
		+SVerticalBox::Slot().AutoHeight()
		[
			SNew(SButton).Text(FText::FromString("Query ObjectPtr.IsValid()"))
			.OnClicked_Lambda([this]()
				{
					if(ObjectPtr.IsValid())
						FMessageDialog::Open(EAppMsgType::Type::Ok, FText::FromString("Is Valid"));
					else
						FMessageDialog::Open(EAppMsgType::Type::Ok, FText::FromString("Not Valid"));
					return FReply::Handled();
				})
		]
	];
		
	FModeToolkit::Init(InitToolkitHost);
}

在这里插入图片描述

实验0:没有使用FGCObject

  1. 点击Create new TestClass,此举生成一个新的FTestClass
  2. 点击Query ObjectPtr.IsValid(),此时显示还没被销毁:
    在这里插入图片描述
  3. 点击Execute GC,此举强制执行了一次GC
  4. 点击Query ObjectPtr.IsValid(),此时显示:
    在这里插入图片描述
    可见已经被GC掉了

实验1:使用 FGCObject 引用UObject

下面将FTestClass继承FGCObject
TestClass.h

#pragma once

#include"CoreMinimal.h"

#include"UObject/GCObject.h"

class FTestClass : public FGCObject
{
public:

	FTestClass();

	//想要引用的对象
	class UTestObject* MyObject;

	//FGCObject的接口:
	virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
};

然后在AddReferencedObjects中加入对MyObject的引用
TestClass.cpp

#include"TestClass.h"

#include"TestObject.h"

FTestClass::FTestClass()
{
	//创建一个新的UObject
	MyObject = NewObject<UTestObject>(GetTransientPackage(), TEXT("TestGCPlgTestObject"));
}

void FTestClass::AddReferencedObjects(FReferenceCollector& Collector)
{
	Collector.AddReferencedObject(MyObject);
}

下面再测试:

  1. 点击Create new TestClass,此举生成一个新的FTestClass
  2. 点击Query ObjectPtr.IsValid(),此时显示还没被销毁
  3. 点击Execute GC,此举强制执行了一次GC
  4. 点击Query ObjectPtr.IsValid(),此时,仍旧没有被销毁:
    在这里插入图片描述
  5. 点击Delete TestClass,此举删除了之前创建的FTestClass
  6. 点击Execute GC,此举强制执行了一次GC
  7. 点击Query ObjectPtr.IsValid(),发现现在销毁了:
    在这里插入图片描述

可见,当FTestClass这个引用UObject的人被销毁后,UObject也会在下次GC时被销毁。

实验2:观察 AddReferencedObjects 的调用时机

我在AddReferencedObjects中加入了断点,希望能断到任何的调用。
在这里插入图片描述
当我点击Create new TestClass创建TestClass时,断点并没有触发

而当我点击Execute GC调用GC时,立马触发了。
在这里插入图片描述
不过看栈,并不是从CollectGarbage出发的,是从另一个线程:
在这里插入图片描述
而且,当下次我调用GC时,AddReferencedObjects被再次调用。

看来,并不用担心开始提到的,引用的对象不会被加入引用的问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值