UObject是一套很復雜的體系,之前讀ue3代碼時曾分析過其類型系統實現,主要是與UClass間的關系
現在轉到ue4,發現那一塊其實差不多,於是再重點備忘一下UObject本身的創建和初始化過程
1、首先,用NewObject<>來創建一個新對象:
template< class T > T* NewObject(UObject* Outer, UClass* Class, FName Name = NAME_None, EObjectFlags Flags = RF_NoFlags, UObject* Template = nullptr, bool bCopyTransientsFromClassDefaults = false, FObjectInstancingGraph* InInstanceGraph = nullptr) { if (Name == NAME_None) { FObjectInitializer::AssertIfInConstructor(Outer, TEXT("NewObject with empty name can't be used to create default subobjects (inside of UObject derived class constructor) as it produces inconsistent object names. Use ObjectInitializer.CreateDefaultSuobject<> instead.")); } #if DO_CHECK // Class was specified explicitly, so needs to be validated CheckIsClassChildOf_Internal(T::StaticClass(), Class); #endif return static_cast<T*>(StaticConstructObject_Internal(Class, Outer, Name, Flags, EInternalObjectFlags::None, Template, bCopyTransientsFromClassDefaults, InInstanceGraph)); }
除了一些條件檢測,直接調了以前老版的對應物StaticConstructObject_Internal。
這里的幾個參數,有意思的是第5個參數Template,通過它可以復制對象。
2、StaticConstructObject_Internal,這里面主要做兩件事:
一是用StaticAllocateObject來分配空間,
二是在此空間上初始化對象。
3、StaticAllocateObject:
首先是檢查要創建新對象,還是替換一個原有對象,因為一路傳進來的有個Name參數。如果找到一個已有的同名對象,那么會強制那個對象析構,然后重用它的空間,否則的話,就會直接分配空間了:
UObjectBase* FUObjectAllocator::AllocateUObject(int32 Size, int32 Alignment, bool bAllowPermanent)
除了這件主要的工作,還有不少其它的事,比如管理對象之間的連接、在異步線程中創建對象時的通知等等,暫時不深究了。
4、有了前者分配的空間,那么就可以在此處構造對象了:
Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject);
(*InClass->ClassConstructor)( FObjectInitializer(Result, InTemplate, bCopyTransientsFromClassDefaults, true, InInstanceGraph) );
這里ClassConstructor是每個UClass都有的一個函數指針類成員變量,實際上所有的UClass里該指針都指向一個全局模板函數:
template<class T> void InternalConstructor( const FObjectInitializer& X ) { T::__DefaultConstructor(X); }
而每個類里的__DefaultConstructor也是用宏統一生成的,內容也是簡單的轉發給new:
static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass(X); }
這個new的形式很不平凡,既有傳給operator new的【(EInternal*)X.GetObj()】,又有傳給該類實際構造函數的【FObjectInitializer& X】
后者正是InClass->ClassConstructor需要的實參,而前者也是通過宏(DECLARE_CLASS)定義在每個UObject里的一個operator new:
inline void* operator new( const size_t InSize, EInternal* InMem ) \ { \ return (void*)InMem; \ }
總結一下上面繞來繞去的東西就是:
StaticAllocateObject分配了內存空間Result,然后以此為參數(當然還有其它的參數)構造了一個FObjectInitializer X,其中X.GetObj返回的就是這個內存地址Result;
接着用【 new(Result) TClass(X) 】來構造對象,指明地址在Result上,並將X做為參數傳給其構造函數。
5、關於FObjectInitializer
接上步,在構造函數返回后,其臨時參數【FObjectInitializer& X】是要自動析構的,因而ue4利用此步驟,在這里面做了很多事,大部份的事都是與該體系內部狀態管理有關的,暫時難以透徹理解。
但有一個的操作與應用相關,即屬性初始化,這是通過InitProperties來完成的:
void FObjectInitializer::InitProperties(UObject* Obj, UClass* DefaultsClass, UObject* DefaultData, bool bCopyTransientsFromClassDefaults) { …… UClass* Class = Obj->GetClass(); ……if (!bNeedInitialize && bCanUsePostConstructLink) { // This is just a fast path for the below in the common case that we are not doing a duplicate or initializing a CDO and this is all native. // We only do it if the DefaultData object is NOT a CDO of the object that's being initialized. CDO data is already initialized in the // object's constructor. if (DefaultData) { if (Class->GetDefaultObject(false) != DefaultData) { QUICK_SCOPE_CYCLE_COUNTER(STAT_InitProperties_FromTemplate); for (UProperty* P = Class->PropertyLink; P; P = P->PropertyLinkNext) { P->CopyCompleteValue_InContainer(Obj, DefaultData); } } else { QUICK_SCOPE_CYCLE_COUNTER(STAT_InitProperties_ConfigEtcOnly); // Copy all properties that require additional initialization (e.g. CPF_Config). for (UProperty* P = Class->PostConstructLink; P; P = P->PostConstructLinkNext) { P->CopyCompleteValue_InContainer(Obj, DefaultData); } } }
……
在這里通過遍歷UClass上記錄的屬性元數據,可以對當前實例的每個屬性進行賦值。
有趣的是DefaultData這個參數,也就是最早的Template參數,一路輾轉到此。當然如果Template為空的話,這里的DefaultData就是該類的CDO了。
代碼里明顯體現出對此兩種情況的不同處理策略:
如果是指定了要復制的對象Template->DefaultData,那么要遍歷類上的所有的屬性,因為對於一個實際的復制目標,你不知道它哪些屬性已經改變了(不是默認值 ),因此必須全盤復制
而如果只是從CDO復制的話,那么只需要處理該類里明確指定過可能有初始化狀態的字段,如打上CPF_Config標記的字段,它們在啟動時會去ini文件里提取相應的配置值。