新功能介绍

UE5引入了 TObjectPtr,一种基于模板的64位指针系统,可选择性地用来取代编辑器版本(editor builds)中的原始对象指针(raw object pointer)。此系统添加了动态解析和访问追踪功能,并且它的效用和非编辑器版本中的原始指针完全相同。TObjectPtr 变量在传递给函数或保存为局部变量时,会自动转换为原始指针。尽管大多数涉及 TObjectPtr 的操作都会把 TObjectPtr 隐式转换成原始指针,但在涉及直接操作引擎类时,你可能需要在少数情况下手动改动。之前很多在 UPROPERTY 中采用原始指针的引擎类现在都改为使用 TObjectPtr。项目中与引擎类交互的地方可能需要少量代码更新,用 TObjectPtr 来取代最原始的指针。例如,AActor 的 RootComponent 属性在UE4中是一个 USceneComponent 指针,但在UE5EA中是 TObjectPtr 类型。少数情况下,你可能需要更新与 RootComponent 直接交互的代码。不过,调用 GetRootComponent 的地方会始终保持不变,因为它的返回类型仍然是 USceneComponent

引入原因

为什么要引入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()

  1. public:
  2. // Sets default values for this actor's properties
  3. ATryTObjPtr();
  4. UPROPERTY(EditAnywhere, BlueprintReadWrite)
  5. UStaticMeshComponent* rawPtrComponent;
  6. UPROPERTY(EditAnywhere, BlueprintReadWrite)
  7. TObjectPtr<UStaticMeshComponent> objPtrComponent;
  8. UPROPERTY(EditAnywhere, BlueprintReadWrite)
  9. TMap<int32,TObjectPtr<UStaticMeshComponent>> ObjPtrMap;
  10. UPROPERTY(EditAnywhere, BlueprintReadWrite)
  11. TObjectPtr<UObject> oriObjPtrComponent;
  12. protected:
  13. // Called when the game starts or when spawned
  14. virtual void BeginPlay() override;
  15. public:
  16. // Called every frame
  17. virtual void Tick(float DeltaTime) override;
  18. UFUNCTION(BlueprintCallable)
  19. void TryReflectionPtr();

};

  1. <a name="QeCR2"></a>
  2. ## 实现文件
  3. ```cpp
  4. // Fill out your copyright notice in the Description page of Project Settings.
  5. #include "TryTObjPtr.h"
  6. #include "Kismet/GameplayStatics.h"
  7. ATryTObjPtr::ATryTObjPtr()
  8. {
  9. PrimaryActorTick.bCanEverTick = true;
  10. }
  11. void ATryTObjPtr::BeginPlay()
  12. {
  13. Super::BeginPlay();
  14. UGameplayStatics::GetPlayerController(this, 0)->EnableInput(nullptr); //启用键盘事件,方便调用入口函数
  15. }
  16. void ATryTObjPtr::Tick(float DeltaTime)
  17. {
  18. Super::Tick(DeltaTime);
  19. if (UGameplayStatics::GetPlayerController(this, 0)->WasInputKeyJustPressed(EKeys::C)) //按下键盘C键时调用测试代码入口函数
  20. {
  21. TryReflectionPtr();
  22. }
  23. }
  24. void ATryTObjPtr::TryReflectionPtr()
  25. {
  26. { //反射原始指针
  27. FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("rawPtrComponent"));
  28. UStaticMeshComponent** reflectComp = reflectVar->ContainerPtrToValuePtr<UStaticMeshComponent*>(this);
  29. if (reflectComp && *reflectComp)
  30. {
  31. UE_LOG(LogTemp, Log, TEXT("This is TryReflectionPtr: %s"), *(*reflectComp)->GetPathName());
  32. }
  33. }
  34. //测试FObjectRef动态解析功能
  35. FObjectRef ObjRef = MakeObjectRef(objPtrComponent.Get());
  36. FObjectPtr fObjPtr(ObjRef);
  37. oriObjPtrComponent = fObjPtr.ToTObjectPtr();
  38. //{ //反射TObjectPtr(二级指针): 崩溃!!!
  39. // FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("oriObjPtrComponent"));
  40. // UObject** reflectComp = reflectVar->ContainerPtrToValuePtr<UObject*>(this);
  41. // if (reflectComp && *reflectComp)
  42. // {
  43. // UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
  44. // }
  45. //}
  46. { //反射TObjectPtr(TObjectPtr<UStaticMeshComponent>
  47. FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("oriObjPtrComponent"));
  48. TObjectPtr<UObject>* reflectComp = reflectVar->ContainerPtrToValuePtr<TObjectPtr<UObject>>(this);
  49. if (reflectComp && *reflectComp)
  50. {
  51. UE_LOG(LogTemp, Log, TEXT("This is TObjectPtr<UStaticMeshComponent>: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
  52. }
  53. }
  54. objPtrComponent = Cast<UStaticMeshComponent>(oriObjPtrComponent);
  55. { //反射TObjectPtr(二级指针)此时已经Resolve过,不会崩溃
  56. FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("objPtrComponent"));
  57. UStaticMeshComponent** reflectComp = reflectVar->ContainerPtrToValuePtr<UStaticMeshComponent*>(this);
  58. if (reflectComp && *reflectComp)
  59. {
  60. UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
  61. }
  62. }
  63. { //反射TObjectPtr(TObjectPtr<UStaticMeshComponent>
  64. FProperty* reflectVar = this->GetClass()->FindPropertyByName(TEXT("objPtrComponent"));
  65. TObjectPtr<UStaticMeshComponent>* reflectComp = reflectVar->ContainerPtrToValuePtr<TObjectPtr<UStaticMeshComponent>>(this);
  66. if (reflectComp && *reflectComp)
  67. {
  68. UE_LOG(LogTemp, Log, TEXT("This is TObjectPtr<UStaticMeshComponent>: %s %p %p"), *(*reflectComp)->GetPathName(), reflectComp, *reflectComp);
  69. }
  70. }
  71. {
  72. //UStaticMeshComponent** foundComp = ObjPtrMap.Find(1); //无法通过编译
  73. TObjectPtr<UStaticMeshComponent>* foundComp = ObjPtrMap.Find(1);
  74. if (foundComp && *foundComp)
  75. {
  76. UE_LOG(LogTemp, Log, TEXT("This is ObjPtrMap.Find(1): %s"), *(*foundComp)->GetPathName());
  77. }
  78. }
  79. {
  80. UE_LOG(LogTemp, Log, TEXT("This is objPtrComponent.GetPath(): %s"), *objPtrComponent.GetPath());
  81. }
  82. }

作者:米淇淋
链接:https://zhuanlan.zhihu.com/p/504115127
unreal官方:https://docs.unrealengine.com/5.0/zh-CN/unreal-engine-5-migration-guide/