UE5 UObject和反射

1. 什么是 UObject?

在虚幻引擎中,几乎所有由引擎管理的类都继承自 UObject

标准 C++ 的 new/delete 在 UE 中是被严格限制的。只要一个类继承了 UObject,它就进入了虚幻的"官方管辖区",从而获得了以下超能力:

  • 垃圾回收(Garbage Collection):引擎会自动追踪其引用计数,自动释放内存。

  • 反射数据支持:其实时元数据(Metadata)可以被引擎在运行时读取。

  • 序列化(Serialization):可以轻松地将对象状态保存到硬盘或从硬盘加载。

  • 网络复制(Replication):其属性可以通过网络自动同步。

2. 反射系统的核心:UHT 与 4 大宏

由于 C++ 原生不支持反射,UE5 采用了一种预编译生成代码 的方案。这个负责扫描代码的工具叫做 UHT(Unreal Header Tool)

当你在编写虚幻 C++ 时,必须遵循特定的语法结构。以下是一个标准的 UObject 派生类声明:

复制代码
#include "CoreMinimal.h"
#include "MyObject.generated.h" // 必须是最后一个 include,由 UHT 自动生成

UCLASS(Blueprintable)
class MYPROJECT_API UMyObject : public UObject
{
    GENERATED_BODY() // 虚幻宏,用来注入反射所需的样板代码

public:
    // UPROPERTY 让引擎知道这个变量的存在
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
    int32 Health;

    // UFUNCTION 让引擎和蓝图可以调用这个函数
    UFUNCTION(BlueprintCallable, Category = "Actions")
    void TakeDamage(int32 Amount);
};

UHT 的工作原理

当你点击编译时,虚幻引擎会先跑一遍 UHT:

  1. UHT 扫描你的头文件,一旦发现 UCLASS()UPROPERTY()UFUNCTION()USTRUCT() 这四大宏,就会开始解析。

  2. UHT 会在后台生成一个对应的 .generated.h.gen.cpp 文件。

  3. 这些自动生成的文件里,包含了大量的注册代码(如:将 Health 变量的名称、类型、内存偏移量注册到引擎全局的反射数据库中)。

  4. 最后,C++ 编译器(如 MSVC/Clang)才真正介入,把你的代码和 UHT 生成的代码一起编译成二进制文件。

3. 反射在运行时的体现:UClass 与元数据

既然数据被注册了,我们在运行时该怎么用它?

每个继承自 UObject 的类,在运行时都有一个且仅有一个对应的 UClass 对象。这个 UClass 就是该类的"元数据说明书"。

复制代码
// 获取运行时元数据的方法:
UMyObject* MyObj = NewObject<UMyObject>();

UClass* ClassPrivate = MyObj->GetClass(); // 运行时获取
UClass* ClassStatic  = UMyObject::StaticClass(); // 编译时获取(两者指向同一个 UClass)

通过这个 UClass 说明书,你可以做到很多标准 C++ 根本做不到的事。例如遍历类的所有属性

复制代码
// 运行时动态遍历 MyObj 的所有成员变量
for (TFieldIterator<FProperty> It(MyObj->GetClass()); It; ++It)
{
    FProperty* Property = *It;
    FName PropName = Property->GetFName();
    FString PropType = Property->GetCPPType();
    
    UE_LOG(LogTemp, Log, TEXT("发现变量名: %s, 类型: %s"), *PropName.ToString(), *PropType);
}

这也是虚幻编辑器(Editor)中"Details(细节)面板"的实现原理:你选中一个 Actor,编辑器通过反射读取这个类的 UClass,发现它有 3 个带有 EditAnywhere 标记的 UPROPERTY,于是就在界面上动态渲染出 3 个输入框。

4. 反射与垃圾回收(GC)的强绑定

作为 C++ 程序员,你一定关心垃圾回收是怎么工作的。虚幻的 GC 是基于标记-清除(Mark-Sweep)算法 的,而它寻找"哪些对象还在被使用"的依据,正是反射

  • 必须被 UPROPERTY() 修饰 :如果你的 UObject* 指针没有加 UPROPERTY() 宏,反射系统就不知道这个指针的存在。在 GC 扫描时,它会认为这个对象"已经没有人引用了",从而直接将其内存释放。此时你的指针就会变成野指针 (导致引发 Access Violation 崩溃)。

  • 防止内存泄漏的正确姿势

    复制代码
    UCLASS()
    class AMyActor : public AActor {
        GENERATED_BODY()
    
        // 正确:GC 能看到它,MyObject 存活
        UPROPERTY()
        UMyObject* SafeObject; 
    
        // 危险:GC 看不到它,MyObject 随时可能被销毁,导致 RawObject 变成野指针
        UMyObject* RawObject; 
    };

总结:心智模型转变

概念 标准 C++ 虚幻 C++
根基类 UObject
内存分配 new MyClass() NewObject<UMyClass>()SpawnActor<AActor>()
生命周期 手动 deletestd::shared_ptr 引擎 GC 自动管理(必须挂载 UPROPERTY()
类型识别 dynamic_cast (依赖 RTTI) Cast<T>() (利用虚幻反射,极其高效)
元数据 通过 UClass 存储,运行时可动态遍历和调用
相关推荐
j7~1 天前
【C++】STL--Vector容器--拆析解剖Vector的实现以及Vector的底层详解(2)
开发语言·c++·动态二维数组·vector深度剖析·vector的实现·杨辉三角形
旖-旎1 天前
《LeetCode 130 被围绕的区域 FloodFill DFS 解法》
c++·算法·深度优先·力扣·floodfill
一只旭宝1 天前
【C++入门精讲22】常见设计模式
c++·设计模式
c++之路1 天前
Bazel C++ 构建系列文档(三):构建第一个 C++ 项目
开发语言·c++
旖-旎1 天前
《LeetCode 695 岛屿的最大面积 FloodFill DFS 解法》
c++·算法·力扣·深度优先遍历·floodfill
森G1 天前
61、信号与槽机制在 TCP 编程中的应用---------网络编程
网络·c++·qt·网络协议·tcp/ip
syagain_zsx1 天前
STL 之 vector 讲练结合
c++·算法
牛油果子哥q1 天前
STL set与map底层精讲,红黑树适配原理、有序去重特性、迭代器遍历、API实战与面试核心考点全解
开发语言·数据结构·c++·面试
奇妙方程式1 天前
2026年第九届GXCPC广西大学生程序设计大赛(热身赛)题解
c++·编程比赛·编程竞赛·gxcpc
Tian_Hang1 天前
C++原型模式(Protype)
开发语言·c++·算法