Unreal - 智能指针
目录
智能指针
- 问题背景: 我们之前提到,在堆(Heap)上用 new 创建了对象,就必须在某个地方用 delete 来销毁它。这个过程完全靠程序员手动管理,极其容易出错。忘记 delete 会导致内存泄漏;对同一个指针 delete 两次会导致程序崩溃;访问一个已经被 delete 的指针(悬挂指针)也是灾难性的。
- 解决方案: 智能指针!它是一个C++对象(一个“管家”),内部包装了一个原始指针(一把“钥匙”)。这个管家对象利用C++的作用域规则,在自己被销毁时(例如函数结束、对象生命周期结束),自动地、确定地 delete 它所管理的指针。
管理 UObject 及其派生类 (如 AActor, UActorComponent)
UObject 是Unreal引擎的核心,所有继承自它的对象都由垃圾回收 (Garbage Collection, GC) 系统统一管理生命周期。我们要做的是正确地“配合”GC工作。
| 指针类型 | 何时使用?(最佳实践) |
|---|---|
TObjectPtr<T> |
【首选/默认】 任何时候你需要一个指向UObject的指针,都应该优先使用它。无论是成员变量还是局部变量。 核心优势: 当它指向的UObject被销毁时, TObjectPtr会自动变为空指针 (nullptr),彻底杜绝了悬挂指针崩溃的风险。 |
UPROPERTY() 宏 |
当你需要防止一个UObject被GC回收时,必须用UPROPERTY()来标记持有它的指针。 黄金组合: UPROPERTY() TObjectPtr<T>。UPROPERTY负责“保活”,TObjectPtr负责“安全访问”。 |
TWeakObjectPtr<T> |
当你需要引用一个UObject,但不想阻止它被GC回收时使用。 核心用途: 避免循环引用。例如,一个子Actor想引用它的父Actor,但不希望因为这个引用导致父Actor永远无法被销毁。 |
TSubclassOf<T> |
【极其常用】 当你需要一个变量来持有某个蓝图或C++类本身,以便后续用它来生成(Spawn)新的实例时。 核心优势: 类型安全。它在编辑器和编译期就能保证你赋给这个变量的,必须是指定的基类 T 或其子类。 |
TSoftObjectPtr<T> |
【软引用-实例】 当你需要引用一个资源(如纹理、网格体、音效),但不想在游戏一开始就把它加载到内存里时使用 核心优势: 延迟加载 (Lazy Load)。它只存一个路径字符串,不占实际资源内存。你需要时才手动加载(同步或异步),是优化内存和启动速度的神器。 |
TSoftClassPtr<T> |
【软引用-类型】 当你需要引用一个蓝图类(以便后续生成),但不想一开始就把这个蓝图类加载进内存时。 核心优势: 它是 TSubclassOf 的“轻量版”。比如你有100种武器,用硬引用(TSubclassOf)会导致100个武器资源全部加载;用软引用(TSoftClassPtr)则只加载路径,玩家选中哪个才加载哪个。 |
阵营二:管理非 UObject 的普通 C++ 类
对于那些你自己创建的、不继承自 UOject 的普通C++类(例如 Slate UI 的逻辑类、自定义的数据管理器等),GC系统完全不管它们。
这时,我们就需要请出C++的传统智能指针来手动管理它们的生命周期。
说是c++的传统指针,其实也是Unreal在于基础上自己封装的
| 指针类型 | 所有权模型 | 何时使用? |
|---|---|---|
TSharedPtr<T> / TSharedRef<T> |
共享所有权 (Shared Ownership) | 【最常用】 当一个对象需要被多个其他对象共同拥有和管理时。它使用引用计数,当最后一个所有者消失时,对象被自动销毁。TSharedRef是保证永不为空的版本。 核心用途:Slate UI, 异步任务, 共享的数据管理器。 |
TWeakPtr<T> |
无所有权 (Non-owning Observer) | 当你需要“观察”一个由TSharedPtr管理的对象,但不想成为它的所有者(即不想增加引用计数)时。 核心用途: 缓存指针,以及打破 TSharedPtr之间的循环引用。 |
TUniquePtr<T> |
独占所有权 (Exclusive Ownership) | 当一个对象在任何时候都只能有一个唯一的所有者时。它非常轻量,性能接近原始指针,但不能被复制,只能被移动 (Move)。 核心用途:工厂函数的返回值,Pimpl惯用法。 |
最终决策流程图
当你需要一个指针时,这样问自己:
- 我要指向的对象是 UObject 吗? (Actor, Widget, Asset等)
- 否 (普通 C++ 类/结构体)
- 我需要独占它吗? (生命周期随我结束)
- 是 -> TUniquePtr
(最高效)
- 是 -> TUniquePtr
- 我需要和其他人共享它吗?
- 是 -> TSharedPtr
(配合 TWeakPtr 破除循环)
- 是 -> TSharedPtr
- 我需要独占它吗? (生命周期随我结束)
- 是 (UObject 体系)
- 它必须现在就在内存里吗? (还是说只是个硬盘路径?)
- 否 (硬盘路径/延迟加载)
- 指向具体资源 (如纹理) -> TSoftObjectPtr
- 指向蓝图类 (如 BP_Enemy) -> TSoftClassPtr
- 指向具体资源 (如纹理) -> TSoftObjectPtr
- 是 (内存中存在的对象)
- 它是“类”还是“实例”?
- 是类 (用于生成) -> TSubclassOf
- 是实例 (活着的对象)
- 我负责“保活”它吗? (我是它的主人?)
- 是 -> UPROPERTY() + TObjectPtr
(强引用) - 否 (我只是引用/观察,它可能死掉)
- 是 -> TWeakObjectPtr
(安全,死后变nullptr)
- 是 -> TWeakObjectPtr
- 是 -> UPROPERTY() + TObjectPtr
- 我负责“保活”它吗? (我是它的主人?)
- 是类 (用于生成) -> TSubclassOf
- 它是“类”还是“实例”?
- 否 (硬盘路径/延迟加载)
- 它必须现在就在内存里吗? (还是说只是个硬盘路径?)
- 否 (普通 C++ 类/结构体)