新功能介绍
UE5引入了 TObjectPtr,一种基于模板的64位指针系统,可选择性地用来取代编辑器版本(editor builds)中的原始对象指针(raw object pointer)。此系统添加了动态解析和访问追踪功能,并且它的效用和非编辑器版本中的原始指针完全相同。TObjectPtr 变量在传递给函数或保存为局部变量时,会自动转换为原始指针。尽管大多数涉及 TObjectPtr 的操作都会把 TObjectPtr 隐式转换成原始指针,但在涉及直接操作引擎类时,你可能需要在少数情况下手动改动。之前很多在 UPROPERTY 中采用原始指针的引擎类现在都改为使用 TObjectPtr。项目中与引擎类交互的地方可能需要少量代码更新,用 TObjectPtr 来取代最原始的指针。例如,AActor 的 RootComponent 属性在UE4中是一个 USceneComponent 指针,但在UE5EA中是 TObjectPtr
引入原因
为什么要引入FObjectPtr/TObjectPtr对象指针类?
在UE4中,很多引擎类型的成员是UObject裸指针,其中只记录内存地址。由于内存地址在每次运行时都会发生变化,这就给编辑器下的序列化静态保存、延迟加载等带来了不便。此外,裸指针也不能提供访问追踪功能,这会在对UObject进行访问Debug时带来极大的不便:需要事先封装Getter/Setter函数(若在出现Bug后再封装则需要涉及代码重构,成本很大),且不能追踪反射等非直接的访问。由于UE5放弃了对32位系统的支持,从而只考虑64位的指针。而在现有的硬件条件下,寻址空间不可能将指针的64位全部用完。因此,这64位就可以用来存储额外的信息:这样就可以把UObject的静态引用关系编码到这64位中,运行时再解析为内存地址。同时,在这种封装中,就可以在访问变量等需要Debug的时机加入Hook点,这样就可以很方便的通过注册Hook函数进行Debug。而在另一方面,在不需要Debug的情况下(如Release版本),这些封装与Hook等新引入的特性不应带来额外的性能开销,它们的效用应该与UObject裸指针没有区别。由此,FObjectPtr的特性应该包含以下方面:
1、信息封装:将Object的静态信息封装到64位中,以便序列化静态保存、延迟加载等。
2、动态解析:将封装的64位信息可以在运行时解析为UObject指针。
3、Debug追踪:在对FObjectPtr进行访问和动态解析时,可以通过Hook的方式获取此次访问/解析的信息以供Debug。
4、还原:在如Release等不需要上述功能的情况下,FObjectPtr应退化为UObject裸指针,不应增加额外性能开销。
TObjectPtr<>使用建议
- 对于需要进行访问追踪的UPROPERTY成员变量,可以使用TObjectPtr
替换裸指针;对于函数参数、局部指针变量等,则建议使用UObject*裸指针。 - 在进行容器Find时、反射访问变量时,捕获类型应与变量声明的类型保持一致,不宜混用TObjectPtr
和T*。 - 注册的Hook函数由于访问频次通常会很高,应保证在大多数情况下的低开销。
代码示范
头文件
```cpp // Fill out your copyright notice in the Description page of Project Settings.
pragma once
include “CoreMinimal.h”
include “GameFramework/Actor.h”
include “TryTObjPtr.generated.h”
UCLASS() class MYFPS_API ATryTObjPtr : public AActor { GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATryTObjPtr();
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UStaticMeshComponent* rawPtrComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UStaticMeshComponent> objPtrComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TMap<int32,TObjectPtr<UStaticMeshComponent>> ObjPtrMap;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TObjectPtr<UObject> oriObjPtrComponent;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
UFUNCTION(BlueprintCallable)
void TryReflectionPtr();
};
<a name="QeCR2"></a>
## 实现文件
```cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "TryTObjPtr.h"
#include "Kismet/GameplayStatics.h"
ATryTObjPtr::ATryTObjPtr()
{
PrimaryActorTick.bCanEverTick = true;
}
void ATryTObjPtr::BeginPlay()
{
Super::BeginPlay();
UGameplayStatics::GetPlayerController(this, 0)->EnableInput(nullptr); //启用键盘事件,方便调用入口函数
}
void ATryTObjPtr::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (UGameplayStatics::GetPlayerController(this, 0)->WasInputKeyJustPressed(EKeys::C)) //按下键盘C键时调用测试代码入口函数
{
TryReflectionPtr();
}
}
void ATryTObjPtr::TryReflectionPtr()
{
{ //反射原始指针
FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("rawPtrComponent"));
UStaticMeshComponent** reflectComp = reflectVar->ContainerPtrToValuePtr<UStaticMeshComponent*>(this);
if (reflectComp && *reflectComp)
{
UE_LOG(LogTemp, Log, TEXT("This is TryReflectionPtr: %s"), *(*reflectComp)->GetPathName());
}
}
//测试FObjectRef动态解析功能
FObjectRef ObjRef = MakeObjectRef(objPtrComponent.Get());
FObjectPtr fObjPtr(ObjRef);
oriObjPtrComponent = fObjPtr.ToTObjectPtr();
//{ //反射TObjectPtr(二级指针): 崩溃!!!
// FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("oriObjPtrComponent"));
// UObject** reflectComp = reflectVar->ContainerPtrToValuePtr<UObject*>(this);
// if (reflectComp && *reflectComp)
// {
// UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
// }
//}
{ //反射TObjectPtr(TObjectPtr<UStaticMeshComponent>)
FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("oriObjPtrComponent"));
TObjectPtr<UObject>* reflectComp = reflectVar->ContainerPtrToValuePtr<TObjectPtr<UObject>>(this);
if (reflectComp && *reflectComp)
{
UE_LOG(LogTemp, Log, TEXT("This is TObjectPtr<UStaticMeshComponent>: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
}
}
objPtrComponent = Cast<UStaticMeshComponent>(oriObjPtrComponent);
{ //反射TObjectPtr(二级指针)此时已经Resolve过,不会崩溃
FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("objPtrComponent"));
UStaticMeshComponent** reflectComp = reflectVar->ContainerPtrToValuePtr<UStaticMeshComponent*>(this);
if (reflectComp && *reflectComp)
{
UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
}
}
{ //反射TObjectPtr(TObjectPtr<UStaticMeshComponent>)
FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("objPtrComponent"));
TObjectPtr<UStaticMeshComponent>* reflectComp = reflectVar->ContainerPtrToValuePtr<TObjectPtr<UStaticMeshComponent>>(this);
if (reflectComp && *reflectComp)
{
UE_LOG(LogTemp, Log, TEXT("This is TObjectPtr<UStaticMeshComponent>: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
}
}
{
//UStaticMeshComponent** foundComp = ObjPtrMap.Find(1); //无法通过编译
TObjectPtr<UStaticMeshComponent>* foundComp = ObjPtrMap.Find(1);
if (foundComp && *foundComp)
{
UE_LOG(LogTemp, Log, TEXT("This is ObjPtrMap.Find(1): %s"), *(*foundComp)->GetPathName());
}
}
{
UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent.GetPath(): %s"), *objPtrComponent.GetPath());
}
}
作者:米淇淋
链接:https://zhuanlan.zhihu.com/p/504115127
unreal官方:https://docs.unrealengine.com/5.0/zh-CN/unreal-engine-5-migration-guide/