UE5 - UC++中的UOject使用详解(创建/持有/销毁UObject实例、获取类信息、类型转换)
作者:hangge | 2026-10-27 09:21
本文主要讲解 UC++ 中的 UObject,UObject 指的是 UClass 实例,实例的类都直接或间接地继承于 UObject 类。这些类除非直接或间接地继承于 AActor,否则名字一律需要以大写字母“U”开头。

(3)假设我们要创建的实例的类是 T,继承于 UObject,具体代码如下。可以看到到 NewObject<T> 函数有两个比较常见的参数:
(4)现在我们来实践一下。在 HelloUE.h 的类体中加入一个新的 UFunction“TestUObject”
(5)接下来切换到 HelloUE.cpp 文件,添加如下,我们使用 NewObject 来创建一个 UTestUObject 类实例。函数的第一个参数是 Outer,也就是持有者,这里我们传入 this 作为持有者,也就是 AHelloUE 这个实例本身。
(2)但是在 UC++ 中,如果是指向 UObject 的指针,一般来说我们还需要给它加一个 UPROPERTY 的宏。这样做最重要目的是避免 UObject 实例被垃圾回收(也称为“被 GC”)。
(3)UC++ 有属于自己的一套 GC 方案。其中,所有 UObject 都是受 UE5 的 GC(Garbage Collection,垃圾搜集)系统管理的。如果想要一个实例不被回收,那就需要这个实例被标记为“被持有”。每一个 UObject 实例在 UE5 中都会有一个“引用计数”,表示它被其他地方引用的次数。当一个实例的引用计数大于 0 时, 我们就可以说这个实例被持有。我们可以通过以下几个方式来增加实例的引用计数,防止实例被回收:
(2)第二种方式是通过 实例->GetClass() 函数来获取。GetClass() 函数会获取实例“实际对应”的类信息。这意味着不管持有该实例的类型是什么,都能够获取到实际对应的类信息。
(2)UClass 中除了类本身的信息,还包括了类中所有 UProperty 和 UFunction 的信息。 拥有这些信息,我们的工作会更方便,比如:
一、基本用法
1,UObject 的功能作用
(1)有一套内存管理机制,减少我们在内存管理上花费的精力。分配在堆上的 UObject 实例的内存可以被 UE 自动接管,并在合适的时候自动回收。
(2)使得 UC++ 和蓝图能够完美地配合,能够把类的成员变量和成员函数导出到蓝图使用。
(3)支持反射功能,可以根据名字获取类、成员变量和成员函数。
(4)支持一些网络同步功能,能够将数据同步到服务器和其他客户端。
2,准备工作
(1)首先我们依据之前的文章创建一个 UC++ 测试类“HelloUE”。
(2)接着我们打开 UE5 编辑器,创建一个继承于 UObject 的类,命名为“TestUObject”

3,创建 UObject 实例
(1)先来看看原生 C++ 中是如何创建类的实例的。在原生 C++ 中,一般我们可以有两种方式来创建一个类的实例:
- 一是将类放在栈上,直接持有类实例(如下面代码中的第二行);
- 二是将类放在堆上,使用 new 关键字来创建,并且持有指向类实例的指针(如下面代码中的第三行)。
class MyClass; MyClass Instance; MyClass* Pointer = new MyClass();
(2)UE5 为创建 UObject 封装了一个函数 NewObject<T>。泛型 T 是要创建的 UObject 的类型,根据泛型 T 的不同,这个函数会在堆上创建一个 T 实例,并返回它的指针。
提示:UC++ 中如果需要创建一个 UTestUObject 类实例,许你第一反应同样是使用 new 关键字加 UTestUObject 类的构造函数来实现,但是这在 UC++ 中不是最推荐的做法。UE5 为创建 UObject 封装了一个函数 NewObject<T>,我们一般用这个函数来代替 new 关键字。
(3)假设我们要创建的实例的类是 T,继承于 UObject,具体代码如下。可以看到到 NewObject<T> 函数有两个比较常见的参数:
- 泛型 T 表示要被实例化的类型。
- UObject* Outer 与蓝图里“Construct Object From Class”节点的 Outer 参数对应, 表示被创建实例的拥有者。
- NewObject<T> 函数有很多重载,在这些重载中,还有其他参数没有被的,有兴趣的小伙伴可以自己研究下。
T* NewInstance = NewObject<T>(Outer);
(4)现在我们来实践一下。在 HelloUE.h 的类体中加入一个新的 UFunction“TestUObject”
UFUNCTION() void TestUObject();
- 并引入"TestUObject.h"这个头文件:
#include "TestUObject.h"
(5)接下来切换到 HelloUE.cpp 文件,添加如下,我们使用 NewObject 来创建一个 UTestUObject 类实例。函数的第一个参数是 Outer,也就是持有者,这里我们传入 this 作为持有者,也就是 AHelloUE 这个实例本身。
void AHelloUE::TestUObject()
{
UTestUObject* Testobject = NewObject<UTestUObject>(this);
}
4,UObject 实例的持有
(1)上面样例我们成功地创建了一个 UTestUObject 的实例并将其赋值给了一个临时变量。如果我们想将这个实例作为成员变量保存下,在原生 C++ 中,我们可以直接在类中创建一个对应类型指针的成员变量来保存引用就行了。
class AHelloUE : public AActor
{
// ...省略其他
UTestUObject* TestObject;
}
(2)但是在 UC++ 中,如果是指向 UObject 的指针,一般来说我们还需要给它加一个 UPROPERTY 的宏。这样做最重要目的是避免 UObject 实例被垃圾回收(也称为“被 GC”)。
UPROPERTY() UTestUObject* TestObject;
(3)UC++ 有属于自己的一套 GC 方案。其中,所有 UObject 都是受 UE5 的 GC(Garbage Collection,垃圾搜集)系统管理的。如果想要一个实例不被回收,那就需要这个实例被标记为“被持有”。每一个 UObject 实例在 UE5 中都会有一个“引用计数”,表示它被其他地方引用的次数。当一个实例的引用计数大于 0 时, 我们就可以说这个实例被持有。我们可以通过以下几个方式来增加实例的引用计数,防止实例被回收:
- 将实例作为成员变量,并且使用 UPROPERTY 修饰,也就是让一个 UProperty 来持有实例。如此一来就意味着“成员变量被类所持有”。这个方法最常用,也最简单。
- 调用 UObject::AddToRoot(),可以将实例添加到 GC 的根,也就是根持有着这个实例。GC 会维护一个实例树,实例树的最顶端就称为“根”。这个方法要谨慎使用, 如果我们只是调用了 AddToRoot(),而忘了在适当的时机调用 RemoveFromRoot() 的话,UObject 就会一直不被回收。
- 被容器类型 TArray、TMap、TSet(直接或间接地)引用(可能是作为元素,或者是直接或间接地作为元素的成员变量)。被容器类型引用会自动添加一个引用计数。
(4)将 UObject 指针类型的成员变量变成 UProperty 之后,它的值会由 UE 接管。这意味着如果 UObject 被回收了,指针的值会被自动设置为 nullptr。如果我们没有将 UObject 指针的成员变量设置成 UProperty,那么有可能 UObject 实例随时会被销毁。而且由于成员变量不是 UProperty,UE 无法感知和控制它的值,所以在实例被销毁后,指针不会自动变为 nullptr,这个时候如果我们仍将它拿来判空,还是会得到它是非空的结果。但是即使此时指针非空,它指向的内存也是被回收了,所以如果我们访问这个指针,就会引发严重错误。所以,在 UObject 指针的成员变量上,最好加上 UPROPERTY 宏,将它变成 UProperty。
二、获取类的信息
1,基本介绍
(1)如果你用过 Java 和 C# 等更高等级的编程语言,可能试过在程序运行期间获取类的信息。这些类的信息非常丰富,包括但不限于:类的名字、类的成员变量、类的成员函数。
(2)在 UC++ 中,如果我们使用了 UClass+UProperty+UFunction 这一套体系,那么 UE 也会帮我们搜集这个 UClass 类的信息,并在一个 UClass 类型的实例中。这里的 UClass 指的是有一个类,它的类名就叫 UClass,用来存储类的信息。
2,两种方式获取类的 UClass
(1)第一种方式是通过类名:StaticClass() 的静态函数获取,这个静态函数会返回一个 UClass 实例的指针。StaticClass() 是由 GENERATED_BODY() 宏动态插入到类体中的静态函数,返回的是类对应的类信息。调用这个函数后,我们可以用一个 UClass* 类型的变量来持有类的信息。
UClass* ClassInfo = UHelloUE::StaticClass();
(2)第二种方式是通过 实例->GetClass() 函数来获取。GetClass() 函数会获取实例“实际对应”的类信息。这意味着不管持有该实例的类型是什么,都能够获取到实际对应的类信息。
- 如下面代码,这里假定我们创建了一个父类 UBase,以及一个子类 Derived 继承于 UBase。我们使用 NewObject 函数创建了一个 Derived 实例,然后使用父类指针 UBase* 来持有子类实例。在对父类指针指向的子类实例调用 GetClass() 函数的时候, GetClass() 函数会返回的是实例的具体类型 UDerived 对应的 UClass,而不是 UBase 的 UClass。
UBase* Instance = NewObject<UDerived>(this); UClass* ClassInfo = Instance->GetClass(); //获取到的是UDerived的类信息
3,通过 UClass 获取类的信息
(1)获取到 UClass 之后,我们可以尝试打印类的名字。下面代码我们调用了 UClass 的 GetName() 函数,这个函数会返回一个 FString 类型的值,内容是类的名称。 我们使用 * 号对 FString 转型成 TCHAR* 后就可以将它使用 UE_LOG 打印出来。
UE_LOG(LogTemp, Log, TEXT("Class name is: %s"), *(ClassInfo->GetName()));
(2)UClass 中除了类本身的信息,还包括了类中所有 UProperty 和 UFunction 的信息。 拥有这些信息,我们的工作会更方便,比如:
- 使用属性的名字来获取 UProperty 对应的值;
- 使用属性的名字来设置 UProperty 对应的值;
- 使用函数的名字来调用函数,传入对应的参数;
- .......还可以做更多的事情,这些就等大家自己去挖掘了。
三、类型转换
1,基本说明
(1)在原生 C++ 中(C++11 标准或以上),我们会使用几种类型转换的函数,这些函数在 UC++ 中也被封装了一层。
(2)其中,最常用的是 Cast<T>,它对应的是原生 C++ 中的 dynamic_cast<T>,用来动态转换类型。Cast<T> 的实现原理和 dynamic_cast<T> 的实现原理不同,不通过 RTTI 来实现,而是通过 UC++ 的类信息系统来实现。
(3)除了 Cast<T> 外,比较常用的类型转换函数还有 StaticCast<T>。StaticCast<T> 对应 static_cast<T>,用来做静态转换。
2,使用样例
下面代码中,父类是 UBase,子类是 UDerived,我们使用父类来持有子类。到了需要的时候,我们可以使用 Cast<T> 将持有类型转换回来。如果转换成功, 会返回相对应的指针,否则返回一个空指针。所以在使用之前,可以对 Cast<T> 返回的值判空。
UBase* InstanceBase = NewObject<UDerived>(this);
UDerived* InstanceDerived = Cast<UDerived>(InstanceBase);
if(InstanceDerived)
{
// ...做某些操作
}
四、类实例的销毁
1,基本介绍
(1)由于 UObject 是纳入了 GC(垃圾回收)机制管理的,我们基本上是没有办法主动去删除一个 UObject 实例,也就是不能对实例调用 delete 来删除一个 UObject 实例(毕竟我们也没有用 new 关键字来创建它)。我们只能期望它在 GC 的时候被回收。
(2)想要一个 UObject 实例被回收,就要保证它没有被任何地方直接或间接地引用。如果我们希望 UObject 没有被持有, 那就需要:
- 检查 UObject 实例没有被 UProperty 成员变量(直接或间接地)引用。如果 UObject 实例被 UProperty 引用了,那么需要将这个引用设置为 nullptr 来释放引用。
- 检查 UObject 有没有被调用过一次或以上的 AddToRoot()。如果有,要调用同样次数的 RemoveFromRoot()。
- 检查 UObject 有没有被任何容器(直接或间接地)引用。如果有,需要将引用从容器中移除。
2,手动发起回收
保证 UObject 实例没有被任何地方引用之后,我们可以尝试使用如下立即发起一次 GC。
GEngine->ForceGarbageCollection(false);
全部评论(0)