news 2026/3/21 13:18:00

《Unreal 对 C++ 做了什么》系列 11. 弱引用与软引用:TWeakObjectPtr 和 TSoftObjectPtr

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
《Unreal 对 C++ 做了什么》系列 11. 弱引用与软引用:TWeakObjectPtr 和 TSoftObjectPtr

《Unreal 对 C++ 做了什么》系列 (11/54)

11. 弱引用与软引用:TWeakObjectPtr 和 TSoftObjectPtr 🔗

🚀 导言:打破“强引用”的垄断

在标准 C++ 中,std::weak_ptr必须配合std::shared_ptr。而在虚幻引擎中,所有UObject的生死由 GC 说了算。

如果我们只使用UPROPERTY标记的原始指针(强引用),会面临两个严峻问题:

  1. 循环引用(Circular Reference):两个对象互相强引用,导致 GC 永远认为它们“可达”,内存永不释放。
  2. 加载链爆炸(Reference Loading Hell):强引用会导致资源被“打包加载”。你引用了一个敌人,引擎会自动把该敌人引用的武器、特效、甚至音效全部塞进内存。

为了解决这些问题,UE 构建了TWeakObjectPtrTSoftObjectPtr


🔑 1. TWeakObjectPtr:安全的“旁观者”

TWeakObjectPtr是一种不参与 GC 追踪的弱引用。它允许你引用一个对象,但不会阻止该对象被回收。

底层黑科技:它是如何感知“死亡”的?

它内部存储的不是内存地址,而是InternalIndex(对象索引)SerialNumber(序列号)

  • 当你通过.Get()访问时,它会去全局对象表(GUObjectArray)中比对该索引下的序列号。
  • 如果序列号不匹配,说明原对象已被销毁,该指针会自动判定为nullptr。这彻底根绝了 C++ 中最恐怖的“野指针”问题。
代码实战:
// 场景:追踪一个可能会被销毁的敌人TWeakObjectPtr<AActor>EnemyWatcher;voidAMyCharacter::Track(AActor*Target){EnemyWatcher=Target;}voidAMyCharacter::Tick(floatDeltaTime){// 必须检查有效性,因为 Target 可能在上一帧被 GC 了if(EnemyWatcher.IsValid()){FVector Loc=EnemyWatcher->GetActorLocation();// 支持重载 -> 操作符}}

🔑 2. TSoftObjectPtr:内存的“节能模式”

TSoftObjectPtr底层存储的是FSoftObjectPath(资产路径字符串)。它让你的 C++ 类与庞大的资产(模型、贴图)之间实现“逻辑关联”而非“内存绑定”。

为什么需要它?

如果你在 C++ 里直接写UPROPERTY() UStaticMesh* Mesh;,那么当你加载这个 C++ 所在的类时,该模型会同步阻塞式加载。如果这种硬引用链条过长,会导致游戏启动或切换地图时出现极长的卡顿。

代码实战:异步加载范式
UPROPERTY(EditAnywhere,Category="Assets")TSoftObjectPtr<UStaticMesh>LazyMesh;voidAMyActor::StartAsyncLoad(){// 1. 检查是否已经在内存中,不在则发起异步请求if(LazyMesh.IsPending()){FStreamableManager&Manager=UAssetManager::GetStreamableManager();Manager.RequestAsyncLoad(LazyMesh.ToSoftObjectPath(),FStreamableDelegate::CreateUObject(this,&AMyActor::OnLoadComplete));}}voidAMyActor::OnLoadComplete(){// 2. 此时 Get() 才会返回真正的指针UStaticMesh*Mesh=LazyMesh.Get();MyComponent->SetStaticMesh(Mesh);}

📊 引用类型深度对比

特性强引用 (UPROPERTY*)弱引用 (TWeakObjectPtr)软引用 (TSoftObjectPtr)
底层存储原始内存地址索引 + 序列号资产路径字符串
对 GC 影响阻止对象被回收不阻止不影响(不加载就不存在)
内存成本高(强制对象驻留内存)极低极低
访问速度极快(直接访问)中(需要一次索引比对)慢(首次访问需加载)
自动置空是(GC 时自动设为 null)是(IsValid 返回 false)否(需手动检查)

🛠️ 实战建议:什么时候该选谁?

  • 父子关系:如果 A 拥有 B(比如角色拥有武器组件),使用强引用
  • 观察关系:如果 A 只是临时关注 B(比如 AI 寻找目标、UI 显示当前选中的对象),使用弱引用
  • 资源配置:对于在编辑器里由策划配置的皮肤、关卡道具、UI 图标,使用软引用
  • 循环引用:如果你发现两个 UObject 互相需要对方的指针,请将其中一方改为弱引用

⚠️ 性能警告

TWeakObjectPtr虽然安全,但它的.Get()操作比原生指针稍慢(存在查表开销)。请避免在Tick函数中对数千个弱引用执行Get()操作,如有必要,请在循环外缓存为本地原生指针。


下一篇预告:《12. UObject 生命周期重载:PostInit, PostLoad, BeginDestroy》

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/15 8:11:02

pytorch深度学习笔记16

目录 摘要 1.AdaGrad 2.RMSProp 3.Adam 摘要 本篇文章继续学习尚硅谷深度学习教程&#xff0c;学习内容是AdaGrad&#xff0c;​​​​​​​RMSProp&#xff0c;Adam 1.AdaGrad AdaGrad&#xff08;Adaptive Gradient&#xff0c;自适应梯度&#xff09;会为每个参数适当…

作者头像 李华
网站建设 2026/3/19 10:10:43

如何用云服务器搭建PUBG服务器?

云服务器搭建PUBG服务器完整指南一、服务器配置要求硬件配置推荐根据PUBG游戏的性能需求&#xff0c;建议选择以下配置&#xff1a;最低配置&#xff1a;CPU&#xff1a;Intel Core i5-4430 / AMD FX-6300内存&#xff1a;8GB RAM存储&#xff1a;50GB可用空间&#xff08;推荐…

作者头像 李华
网站建设 2026/3/19 10:48:49

通信原理篇---常见的几种部分响应

让我们用「声音接力游戏」来彻底搞懂这几类部分响应。这个比喻会让你瞬间理解它们的区别和妙处。核心比喻&#xff1a;声音接力游戏想象一个游戏&#xff1a;一排人站好&#xff0c;第一个人要悄悄传递一串数字&#xff08;比如 1 0 1 1&#xff09;给最后一个人。规则限制&…

作者头像 李华