UE 特有 C++ 惯用法

自动关联目录:UE 特有 C++ 惯用法

1. 定位

UE 的 C++ 是"标准 C++ 减去一部分、加上一套框架":禁用了异常与部分 STL,换来了反射、GC、序列化与跨平台。

一句话定位:UE 里写 C++ 出错,大多不是语法错,而是"用了引擎不允许的机制"或"对象生命周期没交给引擎管"。

2. 禁用了什么、换成了什么

标准 C++UE 的替代原因
异常(try/catch)错误码、ensure / check历史与性能考虑(见 崩溃与 Dump)
RTTI(dynamic_cast)Cast<T>反射系统提供类型信息(见 反射与 UHT)
std::shared_ptr 管 UObjectUPROPERTY + GCGC 用引用图,不是引用计数(见 GC)
new / delete UObjectNewObject / CreateDefaultSubobject必须进 GC 的引用图
std::string 显示文本FText本地化(见 本地化)
部分 STL 容器TArray / TMap / TSet自定义分配器与序列化支持

3. 必须记住的几条

惯用法说明
UPROPERTY() 保活UObject 指针成员不标 UPROPERTY 就不被 GC 视为引用,会被回收
NewObject<T>() 而非 new让对象进 GC
CreateDefaultSubobject 只在构造函数创建子对象组件
TWeakObjectPtr 观察不持有强引用,避免阻止 GC
IsValid() 判空比 != nullptr 更严谨(会检查 pending kill)
Cast<T>() 而非 dynamic_cast引擎的类型转换
TObjectPtr(新版本)逐步替代裸指针,便于编辑器与加载
UPROPERTY()
UMyComponent* Comp;              // 正确:GC 知道它被持有

UMyComponent* RawComp;           // 危险:GC 不知道,可能被回收

TWeakObjectPtr<UMyComponent> WeakComp;   // 观察但不持有
if (WeakComp.IsValid()) { /* 还在 */ }

"不标 UPROPERTY 的 UObject 指针"是 UE 项目里最高频的一类悬垂指针 bug——它看起来能跑,GC 一跑就崩。

4. 命名与类型约定

前缀含义
UUObject 派生
AAActor 派生
SSlate 控件(SWidget)
F普通结构体/非 UObject 类
I接口
E枚举
T模板
bbool 成员
bIsXxx / NumXxx常见命名习惯

5. 委托与计时器

机制用途
DECLARE_DELEGATE 系列单播委托
DECLARE_MULTICAST_DELEGATE多播
DECLARE_EVENT限定发送者的多播
FTimerManager定时器(不是 std::thread 等待)
TFunction / TUniqueFunction可调用包装

委托持有对象时要注意解绑——未移除的委托是最常见的泄漏源之一(见 GC)。

6. 与 UE 各系统的对应

惯用法落在哪篇
反射宏与 UHT反射与 UHT
生命周期与 GCGC 与对象生命周期
容器与字符串容器字符串与内存
模块与子系统模块与 Subsystem
断言与崩溃日志与断言体系、崩溃与 Dump
多线程多线程与 Task Graph

7. 代价与权衡

设计收益代价什么时候不该用
禁用异常二进制小、性能可预测错误处理靠返回值需要 RAII 式异常语义时要换写法
UPROPERTY 引用图GC 自动管理忘了标就崩从不忘标
NewObject受 GC 管理比 new 慢一点纯数据对象可用 TSharedPtr
UE 容器分配器可替换、可序列化与 STL 互操作要转换纯算法代码可用 STL
TWeakObjectPtr不阻止 GC每次要 IsValid确定不会被回收时可用裸指针

8. 踩坑与排查

坑现象怎么验证
UObject 指针没标 UPROPERTY随机崩溃检查所有 UObject 成员
用 new 创建 UObject不被 GC 管、泄漏改用 NewObject
委托未解绑泄漏、回调已销毁对象检查 RemoveAll / 弱引用
用 dynamic_cast编译报错或行为不符改用 Cast<T>
静态变量持有 UObject静态区不在引用图见 GC 篇
用 FString 显示文本无法本地化改 FText
在构造函数里访问 World不可用移到 BeginPlay(见 UObject)
用 nullptr 判断而非 IsValid放过 pending kill改 IsValid

9. 排查顺序

1. 随机崩溃?→ 先查 UObject 指针是否都标了 UPROPERTY
2. 对象被提前回收?→ GC 引用图(见 GC 篇)
3. 泄漏?→ 委托是否解绑、静态变量是否持有
4. 编译错误?→ 是否用了被禁用的机制(异常/RTTI)
5. 本地化问题?→ FText 是否用对

参考