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 存储,运行时可动态遍历和调用
相关推荐
WWTYYDS_6663 小时前
手写 C++ Any 类:深入理解多态与模板
开发语言·c++·算法
拾光Ծ3 小时前
C++11实用的新特性:lambda表达式与包装器function与bind
c++·c++11·lambda·bind·function·函数包装器
Shadow(⊙o⊙)3 小时前
Linux内核级文件系统分析——文件系统入门内核级文章!
linux·运维·服务器·开发语言·c++
cjhbachelor3 小时前
C/C++内存管理
c语言·开发语言·c++
噜噜大王_3 小时前
C++ 类和对象(中):默认成员函数全解
开发语言·c++
Non-existent9875 小时前
海拔批量查询 + 批量 KML 生成工具-WPS 插件 TableGIS 新功能
javascript·c++·excel·wps
咩咦12 小时前
C++学习笔记28:静态成员应用:不用循环求1到n的和
c++·学习笔记·类和对象·static·构造函数·oj·静态成员
EllinY12 小时前
CF2217E Definitely Larger 题解
c++·笔记·算法·构造
筠筠喵呜喵13 小时前
Linux软件开发性能优化
linux·c++·性能优化