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>()
生命周期 手动 delete 或 std::shared_ptr 引擎 GC 自动管理(必须挂载 UPROPERTY())
类型识别 dynamic_cast (依赖 RTTI) Cast<T>() (利用虚幻反射,极其高效)
元数据 无 通过 UClass 存储,运行时可动态遍历和调用
相关推荐
重生之小比特6 小时前
【C++进阶】封装红⿊树实现mymap和myset
开发语言·c++
YYYing.7 小时前
【设计模式系列 (四) 】建造者模式
c++·后端·设计模式·建造者模式·c/c++
羑悻的小杀马特7 小时前
Docker高阶实战:从Redis集群到C++微服务,全面解析镜像优化与生产环境部署+镜像制作常见问题详解
c++·redis·docker·镜像制作·dockefile
别动我齐刘海7 小时前
ROS2 Jazzy + C++ 时间系统——Time / Clock / Duration
linux·c++·人工智能·学习·机器学习·机器人·自动驾驶
重生之小比特7 小时前
【C++进阶】unordered_set 和unordered_map 深度解析
c++·哈希算法
chenbingjie_c15 小时前
C++ 进阶核心知识点总结:模板、内存分区、new/delete、内存池
开发语言·c++
Frank_refuel16 小时前
【C++】Json库介绍和使用
c++
重生之小比特16 小时前
【C++进阶】map和set
java·开发语言·c++
知识分享小能手18 小时前
C++ 学习教程,从入门到精通,C++对象和类 — 详细知识点总结(10)
开发语言·c++·学习
顺顺 尼18 小时前
linux 进程信号(上)
linux·c++·算法