新手避坑指南:用gem5 v21+跑通第一个Hello World模拟(附完整simple.py配置)
2026/6/6 9:36:35
在标准 C++ 中,std::weak_ptr必须配合std::shared_ptr。而在虚幻引擎中,所有UObject的生死由 GC 说了算。
如果我们只使用UPROPERTY标记的原始指针(强引用),会面临两个严峻问题:
为了解决这些问题,UE 构建了TWeakObjectPtr和TSoftObjectPtr。
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();// 支持重载 -> 操作符}}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) | 否(需手动检查) |
TWeakObjectPtr虽然安全,但它的.Get()操作比原生指针稍慢(存在查表开销)。请避免在Tick函数中对数千个弱引用执行Get()操作,如有必要,请在循环外缓存为本地原生指针。
下一篇预告:《12. UObject 生命周期重载:PostInit, PostLoad, BeginDestroy》。