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 存储,运行时可动态遍历和调用
相关推荐
神仙别闹4 小时前
基于 C++ 实现两个有序链表序列的交集
java·c++·链表
hansang_IR6 小时前
【题解】[APIO2023] 赛博乐园 / cyberland
c++·算法·图论
落羽的落羽7 小时前
【AI】快速理解AI应用的相关名词概念
linux·c++·人工智能·python·计算机网络·算法
刚入门的大一新生7 小时前
Linux-进程控制
linux·运维·服务器·c++
刃神太酷啦7 小时前
Redis 进阶核心:持久化 (RDB/AOF)、事务与主从复制全解析----《Hello Redis!》(5)
linux·c语言·数据库·c++·redis·缓存·bootstrap
喜欢吃燃面10 小时前
深入 C++ STL:从 unordered_set与unordered_map到底层哈希表的原理与实现
数据结构·c++·散列表
FlightYe10 小时前
音视频修炼之基础理论(五):AAC格式与解析
android·linux·c++·音视频·aac
ShineWinsu11 小时前
对于MySQL:数据库的操作的解析
linux·数据库·c++·mysql·面试·笔试·库的操作
云小逸11 小时前
C++ 第一阶段:对象、内存与生命周期
开发语言·c++
夜航海图11 小时前
S-57 数据解剖:把一个 ENC 文件拆给你看
c++