UE 中的各类指针

§ 参考资料 共 2 条

几种智能指针

这类指针不能持有 UObject 类型的对象,UObject 类型的对象只能被垃圾回收系统释放

UE 中提供的智能指针有:

  • TSharedPtr<T, ESPMode>
  • TSharedRef<T, ESPMode>
  • TWeakPtr<T, ESPMode>
  • TUniquePtr<T, ESPMode>

其中,第二个模板参数 ESPModeEngine Smart Pointer Mode,只有 NotThreadSafeThreadSafe 两个值,表示是否线程安全

线程安全版本因为使用了原子引用计数,会比默认版本稍慢

这几个概念与 C++ STL 中一致,多了一个 TSharedRef,它和 TSharedPtr 行为类似,唯一的区别就是必须始终引用一个非空对象

垃圾回收与“作用域”

UE 中的垃圾回收使用标记——清除算法,管理所有的 UObject 类型以及 AActor 类型(也继承自 UObject 基类)

GC 系统会把所有的这些对象管理起来,建立一个对象引用图,引用关系在 C++ 代码里面一般由 UPROPERTY(...) 宏确定,如:

UCLASS()
class ASampleActor : public AActor
{
    GENERATED_BODY()
public:
    UPROPERTY()
    UTestGCObject* RawPointer;
};

上面的代码确定了 Pointer 对象会被对应的 ASampleActor 引用

每一次执行 GC 的时候,会执行可达性分析,确定哪些对象没有被引用,即不可达,将这些对象内存释放掉

在 UE 中,创建 UObject 对象只能通过 NewObject(...) 或者 CreateDefaultSubobject(...) 等函数创建,不能使用 new 运算符

在创建对象时,可能会传入 Outer 参数表示新对象的“父对象”,注意,它与垃圾回收无关并不表示引用关系,只用来表示类似“作用域”的概念。即:父对象“销毁”了那么子对象也要“销毁”

不过前面说到 UObject 对象只能由 GC 进行释放,这里的“销毁”实际上是对 GC 系统的提示,告诉 GC 系统这些对象可以进行销毁了,GC 系统会把它们标记成 PendingKill,然后在下一次执行 GC 的时候进行销毁和释放

反射

UE 中通过反射来收集类的

一个继承了 UObject 的类必须有 GENERATE_BODY() 这一行代码,并且有 UCLASS() 注解。然后 include 一下 UHT 生成的头文件

GENERATE_BODY()UCLASS() 这两个宏长这个样子:

// ObjectMacros.h

#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)

#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);

#if UE_BUILD_DOCS || defined(__INTELLISENSE__ )
#define UCLASS(...)
#else
#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
#endif

本质上就是根据文件名等信息生成一个新的宏,新的宏在 UHT 生成的头文件中定义,例如:

#define FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_10_INCLASS_NO_PURE_DECLS \
private: \
	static void StaticRegisterNativesUSceneNiagaraRendererProperties(); \
	friend struct Z_Construct_UClass_USceneNiagaraRendererProperties_Statics; \
public: \
	DECLARE_CLASS(USceneNiagaraRendererProperties, UNiagaraRendererProperties, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/GaussianSplattingXRuntime"), NO_API) \
	DECLARE_SERIALIZER(USceneNiagaraRendererProperties)


#define FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_10_ENHANCED_CONSTRUCTORS \
private: \
	/** Private move- and copy-constructors, should never be used */ \
	USceneNiagaraRendererProperties(USceneNiagaraRendererProperties&&); \
	USceneNiagaraRendererProperties(const USceneNiagaraRendererProperties&); \
public: \
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, USceneNiagaraRendererProperties); \
	DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(USceneNiagaraRendererProperties); \
	DEFINE_DEFAULT_CONSTRUCTOR_CALL(USceneNiagaraRendererProperties) \
	NO_API virtual ~USceneNiagaraRendererProperties();


#define FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_7_PROLOG
#define FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_10_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_10_INCLASS_NO_PURE_DECLS \
	FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_10_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS


template<> GAUSSIANSPLATTINGXRUNTIME_API UClass* StaticClass<class USceneNiagaraRendererProperties>();

可以看到,GENERATED_BODY 宏生成了:

// 生成 Natives 函数相关的东西,实现在 cpp 文件中
static void StaticRegisterNativesUSceneNiagaraRendererProperties();

// 定义了一系列的方法,例如 StaticClass()、StaticPackage()
DECLARE_CLASS(USceneNiagaraRendererProperties, UNiagaraRendererProperties, COMPILED_IN_FLAGS(0), CASTCLASS_None, TEXT("/Script/GaussianSplattingXRuntime"), NO_API)

// 序列化相关,定义了 operator<< 相关运算符
DECLARE_SERIALIZER(USceneNiagaraRendererProperties)

// 私有的构造函数
private:
USceneNiagaraRendererProperties(USceneNiagaraRendererProperties&&);
USceneNiagaraRendererProperties(const USceneNiagaraRendererProperties&);

// 热重载相关
DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, USceneNiagaraRendererProperties);
DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(USceneNiagaraRendererProperties);

// 由于构造函数是私有的,这里通过 placement-new 的方法初始化类对象,生成的代码长这个样子:
// static void __DefaultConstructor(const FObjectInitializer& X)
// {
//   new((EInternal*)X.GetObj())USceneNiagaraRendererProperties;
// }
DEFINE_DEFAULT_CONSTRUCTOR_CALL(USceneNiagaraRendererProperties)

// 析构函数
NO_API virtual ~USceneNiagaraRendererProperties();

在生成的对应 cpp 文件中,大概包括:

//	#define IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(TClass) \
//	FClassRegistrationInfo Z_Registration_Info_UClass_##TClass; \
//	UClass* TClass::GetPrivateStaticClass() \
//	{ \
//		if (!Z_Registration_Info_UClass_##TClass.InnerSingleton) \
//		{ \
//			/* this could be handled with templates, but we want it external to avoid code bloat */ \
//			GetPrivateStaticClassBody( \
//				StaticPackage(), \
//				(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \
//				Z_Registration_Info_UClass_##TClass.InnerSingleton, \
//				StaticRegisterNatives##TClass, \
//				sizeof(TClass), \
//				alignof(TClass), \
//				TClass::StaticClassFlags, \
//				TClass::StaticClassCastFlags(), \
//				TClass::StaticConfigName(), \
//				(UClass::ClassConstructorType)InternalConstructor<TClass>, \
//				(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
//				UOBJECT_CPPCLASS_STATICFUNCTIONS_FORCLASS(TClass), \
//				&TClass::Super::StaticClass, \
//				&TClass::WithinClass::StaticClass \
//			); \
//		} \
//		return Z_Registration_Info_UClass_##TClass.InnerSingleton; \
//	}
// 这个宏定义了 GetPrivateStaticClass() 函数的实现,这个函数在 StaticClass() 方法中被调用
// 它根据类的信息生成了一个 UClass 对象
IMPLEMENT_CLASS_NO_AUTO_REGISTRATION(USceneNiagaraRendererProperties);


// 使用 static 对象会在 main 函数之前构建的特性对类进行注册
// FRegisterCompiledInInfo 在构造时会注册类信息
struct Z_CompiledInDeferFile_FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_Statics
{
    static constexpr FClassRegisterCompiledInInfo ClassInfo[] = {
        {
            Z_Construct_UClass_USceneNiagaraRendererProperties,
            USceneNiagaraRendererProperties::StaticClass,
            TEXT("USceneNiagaraRendererProperties"),
            &Z_Registration_Info_UClass_USceneNiagaraRendererProperties,
            CONSTRUCT_RELOAD_VERSION_INFO(FClassReloadVersionInfo, sizeof(USceneNiagaraRendererProperties), 1467212447U)
        },
	};
};

static FRegisterCompiledInInfo Z_CompiledInDeferFile_FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_1601402316(
    TEXT("/Script/GaussianSplattingXRuntime"),
    Z_CompiledInDeferFile_FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_Statics::ClassInfo,
    UE_ARRAY_COUNT(Z_CompiledInDeferFile_FID_Users_xxx_Desktop_PROGRAMS_ue_UnrealProjects_gs_renderer_Plugins_GaussianSplattingX_Source_GaussianSplattingXRuntime_Public_SceneNiagaraRendererProperties_h_Statics::ClassInfo),
	nullptr, 0,
	nullptr, 0);

以上是收集 UClass 的信息,要收集成员变量的信息,需要 UPROPERTY() 宏,这个宏只是一个标记,具体内容在生成的 cpp 文件中

struct Z_Construct_UClass_USceneBufferAsset_Statics
{
#if WITH_METADATA
	static constexpr UECodeGen_Private::FMetaDataPairParam Class_MetaDataParams[] = {
		{ "BlueprintType", "true" },
		{ "IncludePath", "SceneBufferAsset.h" },
		{ "ModuleRelativePath", "Public/SceneBufferAsset.h" },
	};
	static constexpr UECodeGen_Private::FMetaDataPairParam NewProp_SHDim_MetaData[] = {
#if !UE_BUILD_SHIPPING
		{ "Comment", "..." },
#endif
		{ "ModuleRelativePath", "Public/SceneBufferAsset.h" },
#if !UE_BUILD_SHIPPING
		{ "ToolTip", "..." },
#endif
	};
    
    static const UECodeGen_Private::FUInt32PropertyParams NewProp_SHDim;
    static const UECodeGen_Private::FClassParams ClassParams;
};

const UECodeGen_Private::FUInt32PropertyParams Z_Construct_UClass_USceneBufferAsset_Statics::NewProp_SHDim = { "SHDim", nullptr, (EPropertyFlags)0x0010000000000000, UECodeGen_Private::EPropertyGenFlags::UInt32, RF_Public|RF_Transient|RF_MarkAsNative, nullptr, nullptr, 1, STRUCT_OFFSET(USceneBufferAsset, SHDim), METADATA_PARAMS(UE_ARRAY_COUNT(NewProp_SHDim_MetaData), NewProp_SHDim_MetaData) };

const UECodeGen_Private::FPropertyParamsBase* const Z_Construct_UClass_USceneBufferAsset_Statics::PropPointers[] = {
	(const UECodeGen_Private::FPropertyParamsBase*)&Z_Construct_UClass_USceneBufferAsset_Statics::NewProp_SHDim,
};

const UECodeGen_Private::FClassParams Z_Construct_UClass_USceneBufferAsset_Statics::ClassParams = {
	&USceneBufferAsset::StaticClass,
	nullptr,
	&StaticCppClassTypeInfo,
	DependentSingletons,
	nullptr,
	Z_Construct_UClass_USceneBufferAsset_Statics::PropPointers,
	nullptr,
	UE_ARRAY_COUNT(DependentSingletons),
	0,
	UE_ARRAY_COUNT(Z_Construct_UClass_USceneBufferAsset_Statics::PropPointers),
	0,
	0x001000A0u,
	METADATA_PARAMS(UE_ARRAY_COUNT(Z_Construct_UClass_USceneBufferAsset_Statics::Class_MetaDataParams), Z_Construct_UClass_USceneBufferAsset_Statics::Class_MetaDataParams)
};

UClass* Z_Construct_UClass_USceneBufferAsset()
{
	if (!Z_Registration_Info_UClass_USceneBufferAsset.OuterSingleton)
	{
		UECodeGen_Private::ConstructUClass(Z_Registration_Info_UClass_USceneBufferAsset.OuterSingleton, Z_Construct_UClass_USceneBufferAsset_Statics::ClassParams);
	}
	return Z_Registration_Info_UClass_USceneBufferAsset.OuterSingleton;
}

然后就衔接到 ..._Statics 那个结构体了

普通指针

TObjectPtr<T> 是裸指针的增强版,它在运行时行为几乎和 T* 一样,包括占用空间也是 8 字节,但在 UE 的编辑器和打包流程中,增加了引用跟踪、懒加载、Cook 依赖追踪等额外功能

这些额外跟踪并不在 TObjectPtr<T> 这个类里面实现,而是在对应的模块中。例如引用跟踪是在 GC 系统中实现的,它本身并不具备引用跟踪功能。所以,为了让 GC 系统知道引用关系,必须使用 UE 的反射机制,即对成员变量加上 UPROPERTY() 这个宏

如果不加 UPROPERTY() 修饰,那么 TObjectPtr<T> 引用的对象可能随时(在 GC 窗口时)被销毁,成为悬垂指针

强引用指针

TStrongObjectPtr<T> 是强引用指针,与 TObjectPtr<T> 不同,它会执行引用跟踪,引用的对象在作用域内不会被 GC 销毁

这个类的 Reset(...) 方法是这么写的:

FORCEINLINE_DEBUGGABLE void Reset(ObjectType* InNewObject)
{
	if (InNewObject)
	{
		if (Object == InNewObject)
		{
			return;
		}

		if (Object)
		{
			// UObject type is forward declared, ReleaseRef() is not known.
			// So move the implementation to the cpp file instead.
			UEStrongObjectPtr_Private::ReleaseUObject(Object);
		}
		InNewObject->AddRef();
		Object = InNewObject;
	}
	else
	{
		Reset();
	}
}

可以看到,Reset(...) 方法中旧对象引用减一,新对象引用加一。其中,AddRef() 这个方法是 UObjectBase 基类提供的,不过 UObject 本身并不管理引用计数,它只会存储一个 Index,根据 Index 从全局容器(GUObjectArray)中找到对应的 Item,然后执行这个 Item 的 AddRef() 方法

不能用于 UPROPERTY()

弱引用指针

TWeakObjectPtr<T> 是弱引用的 UObject 指针,无论是否添加 UPROPERTY() 宏,都不会被 GC 系统标记,不影响垃圾回收

如果持有的对象被垃圾回收了,那么返回 nullptr

软引用指针

TSoftObjectPtr<T> 是软引用指针,可以控制资产的加载过程,本质上它存储了资产的路径,在手动调用“加载”时才会把资产加载到内存中

它内部存储了一个 FSoftObjectPath 表示资产路径和一个 FWeakObjectPtr 表示对象指针,一旦发现对象已经被加载,就把它的引用放这里。注意这里的弱指针只是名字叫这个,并不是说软引用指针是弱引用指针

其他与普通引用基本一致

它的 IsValid() 方法用于检查软引用是否指向了一个有效的资源,即检查资源是否已加载并且指针是否有效

IsNull() 方法才是检查是否赋予了一个路径的方法,而不会考虑这个路径对应的资源是否加载

类指针

TSubClassOf<T> 是普通指针的类版本,存储相关的 UClass,但是通常 GC 并不会销毁 UClass 对象,所以强弱没有意义

TSoftClassPtr<T> 是软引用指针的类版本,情况类似