智能指针与 TObjectPtr
Smart Pointers & TObjectPtr
UE 有两套对象寿命管理:普通 C++ 对象用引用计数智能指针(TSharedPtr/TSharedRef/TWeakPtr/TUniquePtr),UObject 由垃圾回收管理,不能放进这些智能指针。UE5 新增的 TObjectPtr 用来替代 UPROPERTY 里的 UObject 裸指针:编辑器构建中提供动态解析和访问跟踪,非编辑器构建中与裸指针表现相同。
两套体系为什么不能混用
官方:这些智能指针类「不能用于 UObject 系统,因为 Unreal 对象使用另一套为游戏代码调优的内存跟踪系统」。(Smart Pointers in Unreal Engine)UObject 的寿命由 GC 根据可达性决定,见 垃圾回收与对象生命周期;非 UObject 的普通 C++ 对象(例如 Slate 控件,见 UMG、Slate 与 Common UI)才用引用计数。
四种智能指针
| 类型 | 所有权 | 要点 |
|---|---|---|
TSharedPtr | 共享,拥有对象 | 最后一个共享指针或共享引用消失时删除对象 |
TSharedRef | 共享,拥有对象 | 和 TSharedPtr 类似,但不能为空 |
TWeakPtr | 不拥有 | 不影响寿命,能打破循环引用;但随时可能变空、没有任何提示 |
TUniquePtr | 独占 | 唯一而明确地拥有对象 |
(Smart Pointers in Unreal Engine)
- 创建:
MakeShared把对象和引用控制器分配在同一块内存里(要求对象有 public 构造函数);MakeShareable从已有的裸指针创建。转换用StaticCastSharedRef/Ptr、ConstCastSharedRef/Ptr。 - 线程安全可选:库里有跨线程管理引用计数的线程安全实现,而且是无锁的;不需要时可以换成非线程安全版本来提升性能。
- 代价:所有操作都是常数时间,但创建和拷贝比裸指针开销大,维护引用计数也要额外的指令周期,所以在渲染等底层引擎代码里不太适用。(Smart Pointers in Unreal Engine)
TObjectPtr
- UE5 引入
TObjectPtr,是基于模板的 64 位指针,在编辑器构建中可选地替代 UObject 裸指针。它在编辑器构建中增加动态解析和访问跟踪,在非编辑器构建中「与裸指针表现相同」。传给函数或存进局部变量时会自动转换成裸指针。(Unreal Engine 5 Migration Guide) - 官方建议:虽然是可选的,但 UCLASS/USTRUCT 中的 UObject 指针属性和容器优先用
TObjectPtr<T>而不是T*。很多引擎类已经改了,例如AActor::RootComponent在 UE5 中是TObjectPtr<USceneComponent>;少数直接访问这类成员的代码需要改,但GetRootComponent()仍返回裸指针。(Unreal Engine 5 Migration Guide)
flowchart TD
Q{"对象是 UObject 吗?"} -->|"是"| G["由 GC 管理:<br/>UPROPERTY TObjectPtr 持有强引用"]
Q -->|"否"| S{"需要共享所有权吗?"}
S -->|"是"| SP["TSharedPtr / TSharedRef<br/>需要观察但不持有时用 TWeakPtr"]
S -->|"否"| UP["TUniquePtr"]上图是选型流程:先看是不是 UObject,是就交给 GC;不是再看是否需要共享所有权。
UObject 弱引用 TWeakObjectPtr
官方 API 的定义:弱对象指针 (Weak Object Pointer) 指向一个 UObject,不影响该对象是否被垃圾回收;对象被回收后它会返回 nullptr。它也不能直接跨网络使用。典型用途就是明确「不想阻止某个对象被回收」的场合。(FWeakObjectPtr / TWeakObjectPtr::IsValid (API))
| 接口 | 官方说明(意译) |
|---|---|
IsValid() | 是否指向存活的 UObject;只在需要时调用,过度解析会有性能开销 |
Get() | 解引用,失效时返回 nullptr |
IsStale() | 曾指向某对象、现在不再指向,且期间未被重新赋值或重置 |
IsExplicitlyNull() | 被显式置空、重置过或从未初始化 |
Pin() / TryPin() | 取得强引用 TStrongObjectPtr;TryPin 只在 GC 未进行时成功 |
内部只存 ObjectIndex 和 ObjectSerialNumber 两个 int32,而不是裸指针;没有 operator bool,官方解释是为了避免频繁解析,建议一个函数里只 Get() 一次。线程:IsValid() 不能在其他线程上调用,因为 GC 标记阶段对象带有 Unreachable 标志,会错误地返回 false;传 bThreadsafeTest=true 时只能得知对象是否已经永久消失。(FWeakObjectPtr / TWeakObjectPtr::IsValid (API))
与 TWeakPtr 的区别:后者属于引用计数体系,只用于非 UObject;TWeakObjectPtr 依赖 GC 的对象表(这是根据两份文档的归纳)。
GEA 的视角:裸指针、智能指针与句柄(卷 II 17.5)
GEA 第 17 章比较了三种对象引用,并对智能指针持保留态度,完整内容见 游戏对象引用与查询:
- 裸指针的三类问题:孤儿对象、悬垂指针(对象删除后指针仍指向已释放内存,可能很久之后才崩溃)、非法指针;引擎在运行时重定位内存时也不能直接用。
- 智能指针可以检测对象已删除、做引用计数;C++11 的
shared_ptr、weak_ptr、unique_ptr以 Boost 为蓝本(Boost 还有scoped_array、shared_array、intrusive_ptr等)。难点包括类型安全、不完整类型、异常时的行为和运行时开销。 - 句柄:全局句柄表的索引加唯一对象 id,对象删除后句柄自动变空,对象移动时只需更新表项,还附带一份所有活动对象的列表。
观点并列:GEA 作者的个人建议是"远离智能指针;非用不可就用成熟实现,不要自己造",理由是他参与的项目自研智能指针直到最后都在修 bug。UE 则提供了一整套自家的智能指针,并建议 UObject 指针属性优先用
TObjectPtr。两者的共同点是:本页也记录了 UE 文档承认智能指针的创建和拷贝比裸指针贵、不太适合渲染等底层代码;而 UE 的实现属于随引擎长期维护的成熟实现,不是"每个项目自己造"。另外,UObject 由垃圾回收管理而非引用计数,TWeakObjectPtr在对象被回收后返回 nullptr,效果上与 GEA 所说"对象删除后自动变成空引用"的句柄相似(对比归纳),见 垃圾回收与对象生命周期。
出处
- Smart Pointers in Unreal Engine UE 5.8 文档
- Unreal Engine 5 Migration Guide UE 5.8 文档(UE5 迁移指南)
- FWeakObjectPtr / TWeakObjectPtr::IsValid (API) UE 5.8 API 参考
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.5 节