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 管 UObject | UPROPERTY + GC | GC 用引用图,不是引用计数(见 GC) |
new / delete UObject | NewObject / 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. 命名与类型约定
| 前缀 | 含义 |
|---|---|
U | UObject 派生 |
A | AActor 派生 |
S | Slate 控件(SWidget) |
F | 普通结构体/非 UObject 类 |
I | 接口 |
E | 枚举 |
T | 模板 |
b | bool 成员 |
bIsXxx / NumXxx | 常见命名习惯 |
5. 委托与计时器
| 机制 | 用途 |
|---|---|
DECLARE_DELEGATE 系列 | 单播委托 |
DECLARE_MULTICAST_DELEGATE | 多播 |
DECLARE_EVENT | 限定发送者的多播 |
FTimerManager | 定时器(不是 std::thread 等待) |
TFunction / TUniqueFunction | 可调用包装 |
委托持有对象时要注意解绑——未移除的委托是最常见的泄漏源之一(见 GC)。
6. 与 UE 各系统的对应
| 惯用法 | 落在哪篇 |
|---|---|
| 反射宏与 UHT | 反射与 UHT |
| 生命周期与 GC | GC 与对象生命周期 |
| 容器与字符串 | 容器字符串与内存 |
| 模块与子系统 | 模块与 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 是否用对参考
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/315b030d.html
更新于:2026年10月10日