中EN
概念页

智能指针与 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 所说"对象删除后自动变成空引用"的句柄相似(对比归纳),见 垃圾回收与对象生命周期。

  1. Smart Pointers in Unreal Engine UE 5.8 文档
  2. Unreal Engine 5 Migration Guide UE 5.8 文档(UE5 迁移指南)
  3. FWeakObjectPtr / TWeakObjectPtr::IsValid (API) UE 5.8 API 参考
  4. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  5. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.5 节

相关页面

游戏开发 · UE5容器与字符串Containers & StringsUE 不用 STL,而用自带的值类型容器:TArray(连续数组)、TMap/TMultiMap(哈希映射)和 TSet(哈希集合),都假定元素可按字节直接搬移。字符串分三种:FName 存索引、比较便宜,FText 用于可本地化的界面文本,FString 可以修改但开销最大。选错类型的代价主要是性能和本地化。游戏开发 · UE5垃圾回收与对象生命周期Garbage Collection & Object LifecycleUE 用标记-清除式的可达性分析管理 UObject 内存:从根集合 (Root Set) 出发沿反射可见的强引用遍历对象图,找不到的对象在下次垃圾回收 (Garbage Collection, GC) 时被删除。因此“谁持有 UPROPERTY 引用”决定了对象活多久;Actor 调用 Destroy() 只是请求销毁,真正释放仍取决于引用是否全部放开。游戏开发 · UE5UObject 与反射系统UObject & Reflection System虚幻引擎 (Unreal Engine) 的对象基类是 UObject。在 C++ 类、属性、函数上加 UCLASS/UPROPERTY/UFUNCTION 等宏,再由构建前的预处理工具虚幻头文件工具 (Unreal Header Tool, UHT) 扫描头文件生成代码,引擎就能在运行时“看见”这些类型——这就是反射系统 (Reflection System)。垃圾回收、序列化、编辑器面板、蓝图、网络复制都建立在它之上。游戏开发 · UE5UMG、Slate 与 Common UIUMG, Slate & Common UISlate 是 UE 平台无关的 C++ 声明式 UI 框架(编辑器即用它构建),UMG 是在其之上的可视化控件蓝图层(UE 5.8 API:UWidget 是所有暴露给 UObject 的被包装 Slate 控件的基类),Common UI 插件再在 UMG 之上补充多层界面、输入路由、手柄导航、平台按键图标和样式资产。官方 UMG 最佳实践强调事件驱动更新而非绑定/Tick、固定分辨率与 DPI 制作、用 Scale Box 而非 Render Transform 做永久缩放。游戏开发自定义内存分配器Custom Memory Allocators内存从两方面影响性能:动态分配(malloc / 全局 new)很慢,以及访问模式主导现代 CPU 的性能。GEA 的经验法则是「尽量少做堆分配,绝不在紧密循环里做堆分配」,并介绍游戏引擎常用的自定义分配器:栈分配器(含双端栈)、池分配器、对齐分配、单帧与双缓冲分配器;以及如何用栈/池避免碎片,或用碎片整理加指针重定位(智能指针、句柄)解决碎片。游戏开发游戏对象引用与查询Game Object References & Queries游戏对象需要唯一 id 来区分、查找和充当通信目标;找到对象后还要能引用它,引用的寿命可以从一次函数调用到许多分钟。GEA 比较三种实现:裸指针最快最简单,但有孤儿对象、悬垂指针、非法指针三类问题,且不能配合内存重定位;智能指针可以检测对象已删除、做引用计数,但很难写对,GEA 个人建议远离它,非用不可就用 C++11 标准库或 Boost 的成熟实现;句柄是全局句柄表的整数索引,加上唯一 id 防止槽位复用后指向错误对象,简单而安全。对象查询则不追求通用数据库,而是为最常用的查询建专门的数据结构(GEA4 卷 II 17.5)。综合游戏中的内存管理Memory Management in Games本页把 wiki 中关于内存的内容串成一条线:内存从两方面影响游戏,一是访问模式(缓存未命中远比计算昂贵),二是分配本身(通用堆分配慢且产生碎片,而主机上没有自动换页的虚拟内存兜底)。对策按生命周期组织:常驻与关卡数据用栈、每帧临时数据用单帧/双缓冲分配器、同尺寸小对象用池或小内存分配器、大小不一的动态对象用堆加碎片整理与句柄;资源按引用计数和分块加载管理,流送系统把世界切成等大的块;UE 则在 UObject 层用垃圾回收管理对象生命周期。最后,没有准确的追踪工具,以上一切都无从验证。各结论的细节与出处见所链接的概念页。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原