§ 参考资料 共 2 条
几种智能指针
这类指针不能持有 UObject 类型的对象,UObject 类型的对象只能被垃圾回收系统释放
UE 中提供的智能指针有:
TSharedPtr<T, ESPMode>TSharedRef<T, ESPMode>TWeakPtr<T, ESPMode>TUniquePtr<T, ESPMode>
其中,第二个模板参数 ESPMode 即 Engine Smart Pointer Mode,只有 NotThreadSafe 和 ThreadSafe 两个值,表示是否线程安全
线程安全版本因为使用了原子引用计数,会比默认版本稍慢
这几个概念与 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> 是软引用指针的类版本,情况类似