天天看點

虛幻四源碼閱讀UObjectBase,UObjectBaseUtility,UObjectBaseObject心得

新手,有錯請指出,大家共同進步

Object

UObjectBase

說明

Unreal中UObject的基類

屬性

類型 名字 說明
EObjectFlags ObjectFlags ObjectFlags是用于跟蹤和記錄對象的各種狀态,它被引擎用于表示對象加載、儲存、編輯、垃圾回收和對象作用辨別時候使用。簡單地說,就是一個标記資訊。
int32 InternalIndex 全局對象數組的下标,unreal在運作時候會維護一個全局的對象數組,每當建立一個對象的時候便放到數組裡面
UClass* ClassPrivate 類型資訊
FName NamePrivate 對象名字
UObject* OuterPrivate Outer對象的指針,暫時沒弄明白是啥意思。(update)一個對象A的"Outer"是擁有A的對象。例如,Component由其Actor或父元件擁有,Actors由其Levels擁有。 無論何時構造從UObject派生的類的對象,都要為它提供外部。 (CreateDefaultSubobject隐式提供目前對象作為外部。)可不可以這樣說,人是一個對象,手是一個對象,而手的outer是人? 看這裡解釋

構造函數

UObjectBase

()

這個構造函數,隻把

NamePrivate

設定為

NoInit

,其他啥事都沒有做

UObjectBase

( EObjectFlags InFlags )

Constructor used for bootstrapping

這時候對象并沒有加入到全局對象數組中,感覺有特殊用途

UObjectBase

(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)

UObjectBase::UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)
:	ObjectFlags			(InFlags)
,	InternalIndex		(INDEX_NONE)
,	ClassPrivate		(InClass)
,	OuterPrivate		(InOuter)
#if ENABLE_STATNAMEDEVENTS_UOBJECT
, StatIDStringStorage(nullptr)
#endif
{
	check(ClassPrivate);
	// Add to global table.
	AddObject(InName, InInternalFlags);
}
           

這個構造函數是用于建立靜态配置設定的對象,應該是一般的對象,構造函數把屬性都指派了,但是值得注意的是,

InternalIndex

的值為

INDEX_NONE

,也就是

-1

,并沒有馬上就把對象加入了全局對象數組中。是不是說,建立該對象前,不能知道

InternalIndex

? EInternalObjectFlags标記存放在全局對象管理數組的元素中(這是為了提高Cache命中率)

後面調用

check(ClassPrivate)

來判斷classPrivate是否為NULL

最後

AddObject(InName, InInternalFlags)

把對象加入到全局對象數組中

析構函數

~UObjectBase()

/**
 * Final destructor, removes the object from the object array, and indirectly, from any annotations
 **/
UObjectBase::~UObjectBase()
{
	// If not initialized, skip out.
	if( UObjectInitialized() && ClassPrivate && !GIsCriticalError )
	{
		// Validate it.
		check(IsValidLowLevel());
		LowLevelRename(NAME_None);
		GUObjectArray.FreeUObjectIndex(this);
	}

#if ENABLE_STATNAMEDEVENTS_UOBJECT
	delete[] StatIDStringStorage;
	StatIDStringStorage = nullptr;
#endif
}
           

通過

LowLevelRename(NAME_None)

改掉名字,通過

GUObjectArray.FreeUObjectIndex(this)

把對象從全局對象數組中去掉

函數

void AtomicallyClearFlags

(EObjectFlags FlagsToClear)

/**
 *	Atomically clears the specified flags.
 *	Do not use unless you know what you are doing.
 *	Designed to be used only by parallel GC and UObject loading thread.
 */
FORCENOINLINE void AtomicallyClearFlags( EObjectFlags FlagsToClear )
{
	int32 OldFlags = 0;
	int32 NewFlags = 0;
	do 
	{
		OldFlags = ObjectFlags;
		NewFlags = OldFlags & ~FlagsToClear;
	}
	while( FPlatformAtomics::InterlockedCompareExchange( (int32*)&ObjectFlags, NewFlags, OldFlags) != OldFlags );
}
           

原子級别上清理flags,是不是能了解成在flags上的位上進行清理操作呢?為什麼要這樣操作?

void AtomicallySetFlags

(EObjectFlags FlagsToAdd)

/**
 *	Atomically adds the specified flags.
 *	Do not use unless you know what you are doing.
 *	Designed to be used only by parallel GC and UObject loading thread.
 */
FORCENOINLINE void AtomicallySetFlags( EObjectFlags FlagsToAdd )
{
	int32 OldFlags = 0;
	int32 NewFlags = 0;
	do 
	{
		OldFlags = ObjectFlags;
		NewFlags = OldFlags | FlagsToAdd;
	}
	while( FPlatformAtomics::InterlockedCompareExchange( (int32*)&ObjectFlags, NewFlags, OldFlags) != OldFlags );
}
           

原子級别上清理flags,***是不是能了解成在flags上的位上進行清理操作呢?***為什麼要這樣操作?

void DeferredRegister

(

UClass * UClassStaticClass,

const TCHAR * PackageName,

const TCHAR * Name

)

/**
 * Convert a boot-strap registered class into a real one, add to uobject array, etc
 *
 * @param UClassStaticClass Now that it is known, fill in UClass::StaticClass() as the class
 */
void UObjectBase::DeferredRegister(UClass *UClassStaticClass,const TCHAR* PackageName,const TCHAR* InName)
{
    //檢查該對象是否已經初始化,當UObjectBaseInit()調用後,就初始化了。
	check(Internal::GObjInitialized);
	// Set object properties.
    
    //根據PackageName找到Outer,然後設定本對象的Outer
	UPackage* Package = CreatePackage(nullptr, PackageName);
	check(Package);
	Package->SetPackageFlags(PKG_CompiledIn);
	OuterPrivate = Package;

	check(UClassStaticClass);
	check(!ClassPrivate);
	ClassPrivate = UClassStaticClass;

	// Add to the global object table.
	AddObject(FName(InName), EInternalObjectFlags::None);
    
	//這裡是一些判斷,確定該對象現在不會被垃圾回收,和已經放到root of set裡面了
	// Make sure that objects disregarded for GC are part of root set.
	check(!GUObjectArray.IsDisregardForGC(this) || 		 GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());
}
           

此函數的目的是把通過構造函數

UObjectBase( EObjectFlags InFlags )

建立的boot-strap對象轉化為真實的對象,也就是轉化為跟通過構造函數

UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject *InOuter, FName InName)

建立的對象一樣

函數中保全了類的屬性,然後通過

AddObject

把對象加入全局對象數組中。

void EmitBaseReferences

(

UClass * RootClass

)

Emit GC tokens for UObjectBase , this might be UObject::StaticClass or Default__Class

void UObjectBase::EmitBaseReferences(UClass *RootClass)
{
	static const FName ClassPropertyName(TEXT("Class"));
	static const FName OuterPropertyName(TEXT("Outer"));
	RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, ClassPrivate), ClassPropertyName);
	RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, OuterPrivate), OuterPropertyName, GCRT_PersistentObject);
}
           

GET

FORCEINLINE uint32 GetUniqueID() const
{
	return (uint32)InternalIndex;
}
FORCEINLINE UClass* GetClass() const
{
	return ClassPrivate;
}
FORCEINLINE UObject* GetOuter() const
{
	return OuterPrivate;
}
FORCEINLINE FName GetFName() const
{
	return NamePrivate;
}
           

不多說。。。

bool IsValidLowLevel

()

/**
 * Checks to see if the object appears to be valid
 * @return true if this appears to be a valid object
 */
bool UObjectBase::IsValidLowLevel() const
{
	if( this == nullptr )
	{
		UE_LOG(LogUObjectBase, Warning, TEXT("NULL object") );
		return false;
	}
	if( !ClassPrivate )
	{
		UE_LOG(LogUObjectBase, Warning, TEXT("Object is not registered") );
		return false;
	}
	return GUObjectArray.IsValid(this);
}
           

檢查該對象是否為valid

bool IsValidLowLevelFast

(

bool bRecursive

)

一個能比

IsValidLowLevel

更快速檢查是否為

valid

的函數

void LowLevelRename

(

FName NewName,

UObject * NewOuter

)

/**
 * Just change the FName and Outer and rehash into name hash tables. For use by higher level rename functions.
 *
 * @param NewName	new name for this object
 * @param NewOuter	new outer for this object, if NULL, outer will be unchanged
 */
void UObjectBase::LowLevelRename(FName NewName,UObject *NewOuter)
{
	STAT(StatID = TStatId();) // reset the stat id since this thing now has a different name
	UnhashObject(this);
	check(InternalIndex >= 0);
	NamePrivate = NewName;
	if (NewOuter)
	{
		OuterPrivate = NewOuter;
	}
	HashObject(this);
}

           

改變FName和Outer。值得注意的是,name hash table也要改變。

FName在初始化時候,會根據字元串計算出hash值,然後把該hash值傳入name hash table。然後要對比兩個FName,就隻需要對比兩個hash值就行了。***這樣會加快速度?***參照連結

void Register

(

const TCHAR * PackageName,

const TCHAR * Name

)

/** Enqueue the registration for this object. */
void UObjectBase::Register(const TCHAR* PackageName,const TCHAR* InName)
{
	TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();

	FPendingRegistrant* PendingRegistration = new FPendingRegistrant(this);
	PendingRegistrants.Add(this, FPendingRegistrantInfo(InName, PackageName));
	if(GLastPendingRegistrant)
	{
		GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
	}
	else
	{
		check(!GFirstPendingRegistrant);
		GFirstPendingRegistrant = PendingRegistration;
	}
	GLastPendingRegistrant = PendingRegistration;
}

           

排隊注冊

void RegisterDependencies

()

/** Force any base classes to be registered first */
virtual void RegisterDependencies() {}

           

先注冊。。。***跟上面有什麼不一樣的?***函數體為空?

void SetFlagsTo

(

EObjectFlags NewFlags

)

/**
	 * Set the object flags directly
	 *
	 **/
	FORCEINLINE void SetFlagsTo( EObjectFlags NewFlags )
	{
		checkfSlow((NewFlags & ~RF_AllFlags) == 0, TEXT("%s flagged as 0x%x but is trying to set flags to RF_AllFlags"), *GetFName().ToString(), (int)ObjectFlags);
		ObjectFlags = NewFlags;
	}

           

直接設定flags

void UObjectBaseInit

()

/**
 * Final phase of UObject initialization. all auto register objects are added to the main data structures.
 */
void UObjectBaseInit()
{
    //下面四個初始化為0,但是遲點在.ini檔案中讀取(這個應該是開發遊戲的程式員自行設定的)
	// Zero initialize and later on get value from .ini so it is overridable per game/ platform...
	int32 MaxObjectsNotConsideredByGC = 0;
	int32 SizeOfPermanentObjectPool = 0;
	int32 MaxUObjects = 2 * 1024 * 1024; // Default to ~2M UObjects
	bool bPreAllocateUObjectArray = false;	

	// To properly set MaxObjectsNotConsideredByGC look for "Log: XXX objects as part of root set at end of initial load."
	// in your log file. This is being logged from LaunchEnglineLoop after objects have been added to the root set. 

	// Disregard for GC relies on seekfree loading for interaction with linkers. We also don't want to use it in the Editor, for which
	// FPlatformProperties::RequiresCookedData() will be false. Please note that GIsEditor and FApp::IsGame() are not valid at this point.
	if (FPlatformProperties::RequiresCookedData())
	{
		FString Value;
		bool bIsCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("-filehostip="), Value);
		if (bIsCookOnTheFly)
		{
			extern int32 GCreateGCClusters;
			GCreateGCClusters = false;
		}
		else
		{
			GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsNotConsideredByGC"), MaxObjectsNotConsideredByGC, GEngineIni);

			// Not used on PC as in-place creation inside bigger pool interacts with the exit purge and deleting UObject directly.
			GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.SizeOfPermanentObjectPool"), SizeOfPermanentObjectPool, GEngineIni);
		}

		// Maximum number of UObjects in cooked game
		GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInGame"), MaxUObjects, GEngineIni);

		// If true, the UObjectArray will pre-allocate all entries for UObject pointers
		GConfig->GetBool(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.PreAllocateUObjectArray"), bPreAllocateUObjectArray, GEngineIni);
	}
	else
	{
#if IS_PROGRAM
		// Maximum number of UObjects for programs can be low
		MaxUObjects = 100000; // Default to 100K for programs
		GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInProgram"), MaxUObjects, GEngineIni);
#else
		// Maximum number of UObjects in the editor
		GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInEditor"), MaxUObjects, GEngineIni);
#endif
	}


	// Log what we're doing to track down what really happens as log in LaunchEngineLoop doesn't report those settings in pristine form.
	UE_LOG(LogInit, Log, TEXT("%s for max %d objects, including %i objects not considered by GC, pre-allocating %i bytes for permanent pool."), 
		bPreAllocateUObjectArray ? TEXT("Pre-allocating") : TEXT("Presizing"),
		MaxUObjects, MaxObjectsNotConsideredByGC, SizeOfPermanentObjectPool);

	GUObjectAllocator.AllocatePermanentObjectPool(SizeOfPermanentObjectPool);
	GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC, bPreAllocateUObjectArray);

	void InitAsyncThread();
	InitAsyncThread();

    //注意在這裡表明初始化了
	// Note initialized.
	Internal::GObjInitialized = true;

	UObjectProcessRegistrants();
}

           

這個函數主要做了4件事: 1. 初始化UObject的記憶體配置設定存儲系統和對象的Hash系統。 2. 建立了異步加載線程,用來後續Package(uasset)的加載。 3. GObjInitialized=true,這樣在後續就可以用

bool UObjectInitialized()

來判斷對象系統是否可用。 4. 繼續轉發到

UObjectProcessRegistrants

來把注冊項一一處理。

UObjectBaseUtility

說明

該類繼承了UObjectBase,為UObject類定義了隻與UObject類有關的函數,提供了一些輔助功能,Flag設定和查詢、Mark設定與查詢、Class查詢、名字查詢、Linker資訊(Linker為uasset加載器)

屬性

構造函數

UObjectBaseUtility

空的,啥事都沒做

UObjectBaseUtiliy

(EObjectFlags InFlags)

隻是設定了Inflags

函數

AddToCluster
傳回值 參數 說明
void UObjectBaseUtility* ClusterRootOrObjectFromCluster, bool bAddAsMutableObject = false 把對象放進叢集裡面,便于垃圾回收
AddToRoot
傳回值 參數 說明
void 把對象放到全局對象數組的根集合裡面,其實應該就是放到全局數組對象的前面,可以防止對象被垃圾回收
AppendName
傳回值 參數 說明
void FString & ResultString 幫對象增長名字
AtomicallyClearInternalFlags
傳回值 參數 說明
bool EInternalObjectFlags FlagsToClear 幫對象清理掉過時的flag
CanBeClusterRoot
傳回值 參數 說明
bool 判斷該對象是否能作為一個cluster的根
CanBeInCluster
傳回值 參數 說明
bool 判斷該對象是否能增加到一個cluster裡面
clearFlags
傳回值 參數 說明
void EobjectFlags newFlags 清理掉該對象的某個flag
clearInternalFlags
傳回值 參數 說明
void EInternalObjectFlags FlagsToClear 清理掉該對象的internal flags
ClearPendingKill
傳回值 參數 說明
void EInternalObjectFlags FlagsToClear 清理掉flag中的PendingKill位,也就是說取消殺死該對象
CreateCluster
傳回值 參數 說明
void NULL 建立一個新的對象Cluster
CreateClusterFromObject
傳回值 參數 說明
void UObjectBaseUtility* ClusterRootObject, UObjectBaseUtility* ReferencingObject 清理掉該對象的internal flags
FindNearestCommonBaseClass
傳回值 參數 說明
const UClass* const UClass * TestClass 找到該類和TestClass共同的最近的基類
GetAllMarks
傳回值 參數 說明
EObjectMark NULL 傳回該對象的所有marks
GetFullGroupName
傳回值 參數 說明
FString bool bStartWithOuter 傳回最外面的包到該對象路徑名(除去最外面的包名)
GetFullName
傳回值 參數 說明
FString const UObject * StopOuter 傳回最外面的包到該對象的對象名
GetInterfaceAddress
傳回值 參數 說明
void * UClass * InterfaceClass 傳回一個指向該對象的指針,該指針已經安全地轉換成了特定接口類的指針類型
other function
函數名 傳回值 參數 說明
GetInternalFlags EInternalObj 傳回該對象的internal flags
GetLinker GetInternalFlags 傳回對象的linker
GetLinkerCustomVersion int32 FGuid CustomVersionKey 根據自定義版本的key,傳回自定義版本的linker
GetLinkerIndex int32 傳回該對象的linker索引
GetLinkerLicenseeUE4Version() int32 傳回該函數的linker的證書的UE4版本
GetMaskedFlags EObjectFlags EObjectFlags Mask 将對象的flag與掩碼(mask)作與運算,傳回結果
GetName FString & ResultString 将該對象的名字寫入ResultString,能夠避免重複建立一個FString對象,達到省記憶體目的
GetName FString 傳回該對象的名字
GetNativeInterfaceAddress void* UClass * InterfaceClass 傳回一個指向該對象實作的InterfaceClass本機接口對象的指針
GetNativeInterfaceAddress const void* UClass * InterfaceClass 傳回一個指向該對象實作的const InterfaceClass本機接口對象的指針
GetOutermost UPackage* 傳回一個指向該對象最外面的非NULL outer
GetPathName FString const UObject * StopOuter 傳回該對象的路徑名
GetPathName void const UObject * StopOuter, FString & ResultString 傳回該對象的路徑名,将路徑名寫入ResultString裡面,避免多一個FString
GetTypedOuter UObject * UClass* Target 往上周遊該對象的outer,傳回第一個類型為Target的對象
GetTypedOuter T * 往上周遊該對象的outer,傳回第一個類型為Target的對象
HasAllFlags bool EObjectFlags FlagsToCheck 判斷該對象的flag是否含有FlagsToCheck裡面的所有
HasAllMarks bool EObjectMark Marks 判斷是否含有所有的marks
HasAnyFlags bool EObjectFlags FlagsToCheck 檢查是否含有FlagsToCheck裡面的任何一個
other
函數名 傳回值 參數 說明
HasAnyInternalFlags bool EInternalObjectFlags FlagsToCheck 判斷是否含有FlagsToCheck裡面的任何一個
HasAnyMarks bool EObjectMark Marks 判斷是否含有marks裡面的任何一個
IsA bool OtherClassType SomeBase 判斷該對象是否為某一個類型
IsDefaultSubobject() bool 判斷該對象是否是一個元件/子對象的模闆或者模闆的執行個體
IsIn bool const UObject * SomeOuter 判斷該對象是否在SomeOuter的outer鍊中
IsInA bool const UClass * SomeBaseClass 判斷該對象的outer鍊中是否有SomeBaseClass類型的class
IsNative bool 判斷該對象是否是本地的
IsPendingKill bool 判斷該對象是否是已經被殺死,但是還在記憶體中
IsPendingKillOrUnreachable bool 判斷該對象是否是pandingKilling或者已經無法使用了
IsRooted bool 判斷該對象是否位于全局數組對象的根部集合
IsTemplate bool EObjectFlags TemplateTypes 判斷該對象是否是TemplateType類型的模闆對象
IsUnreachable bool 判斷該對象是否是unreachable的
Mark void EObjectMark Marks 為該對象增加mark
MarkPackageDirty bool 找到該對象最上的outer,并标記為髒
MarkPendingKill void 标記該對象的為RF_PendingKill
OnClusterMarkedAsPendingKill void 如果該cluster已經被标記為PendingKill,那麼當垃圾回收該cluster即将被摧毀時,該函數會被調用,以執行一些額外的清理工作
RemoveFromRoot void 将該對象從全局對象數組的根集合上去掉
RootPackageHasAnyFlags bool uint32 CheckFlagMask 判斷該對象的最上的包是否有CheckFlagMask上的任何一個flag
SetFlags void EObjectFlags NewFlags 為該對象設定新的flag
SetInternalFlags void EInternalObjectFlags FlagsToSet 清理掉過時的内部flags
ThisThreadAtomicallyClearedRFUnreachable bool 清理掉沒用的flag,如果在清理RF_Unreachable的線程中,就傳回true
UnMark void EObjectMark Marks 取消該對象的Mark标記
operator< bool const UObjectBaseUtility & Other 判斷該對象的名字與Other的名字是否按字典順序小

UObject

說明

該類派生于UObjectBaseUtility,是所有其他unreal對象的基類,提供了如下功能:建立子對象(SubObject),對象Destroy相關事件處理,對象編輯相關事件處理,序列化,執行腳本,從config檔案讀取或儲存成員變量配置等等,本次因為函數衆多(100+),就不一一描述了。

屬性

主要函數

函數名 傳回值 參數 說明
AbortInsideMemberFunction void 在調用堆棧的頂部中止成員函數調用,幫助確定大多數平台将此對象的記憶體填充到生成的minidump中
AddReferencedObjects void UObject * InThis, FReferenceCollector & Collector 用于允許對象注冊直接對象引用,該引用尚未被token stream覆寫
AreNativePropertiesIdenticalTo bool UObject * Other 判斷本地的屬性是否與傳入的屬性相同
BeginDestroy() void 當該對象被摧毀前調用

心得

本次實驗從最底層開始,一步一步地抽象,進而逐漸了解UE4。網上教程說UObject是UE4萬物起源,但實際上UObject還是有其基類,UE4的一切其實從UObjectBase開始,UObjectBase其實隻定義了極少數的功能,包括記錄了對象的狀态和名字等等,這些其實在直覺上并沒有與我們遊戲中感受到的東西有太大關系,并沒有場景管理、光照、碰撞等功能,一切都顯得十分原始。但是UObjectBase并不是獨立地完成它的工作,它也必須依賴其他的類來完成它的工作,例如GUObjectArray類。

UObjectBaseUtility繼承自UObjectBase,它的功能又強大了一些,提供了一些輔助功能,Flag設定和查詢、Mark設定與查詢、Class查詢、名字查詢、Linker資訊查詢等等。這些功能更多是UObjectBase的一些補充,讓UE4對象的功能更強。

而到了UObject,它是UE4其他所有對象的基類,提供了如下功能:建立子對象(SubObject),對象Destroy相關事件處理,對象編輯相關事件處理,序列化,執行腳本,從config檔案讀取或儲存成員變量配置等等。這些功能相比它的基類又顯得抽象一些,功能上更強大和複雜起來,而且離計算機底層的距離又遠了一些。因為要實作的功能越來越多,UObject的代碼也更複雜和臃腫,類之間的依賴關系越來越多,代碼閱讀起來也更難。

總的來說,本次源碼閱讀給我上了一堂十分有益的面向對象思想。以前編寫代碼的時候都比較偏向于面向過程,一個函數完成很多功能,當代碼量比較少的時候這種方法并沒有什麼弊端,但是代碼量大的時候弊端就很多。現在讀了UE4的一點點源碼,一開始時候覺得不能了解,為什麼一個object基類要寫成三個這麼麻煩呢?其中的代價是否值得呢?後來當源碼看多了後就慢慢覺得這是十分值得的,因為這降低了出錯的幾率,統一了通用的屬性和接口,也友善其他人員了解。

作為一個完整的object基類,它基本上封裝好了與對象有關的所有基本和共有的操作,是以後面由它派生的類就能很友善的用上它的功能,它的派生類,例如涉及場景管理和角色管理的類,互相協作,進而支撐起UE4龐大的系統。

以下是從網上大佬拷貝下來的引入object基類的作用:

https://www.cnblogs.com/fjz13/p/6164443.html

那麼引入一個Object的根基類設計到底有什麼深遠的影響,我們又付出了什麼代價?

得到:

  1. **萬物可追蹤。**有了一個統一基類Object,我們就可以根據一個object類型指針追蹤到所有的派生對象。如果願意,我們都可以把目前的所有對象都周遊出來。按照純面向對象的思想,萬物皆是對象,是以有一個基類Object會大大友善管理。如果再加上一些機制,我們甚至可以把系統中的所有對象的引用圖給展示出來。
  2. **通用的屬性和接口。**得益于繼承機制,我們可以在object裡加上我們想應用于所有對象的屬性和接口,包括但不限于:Equals、Clone、GetHashCode、ToString、GetName、GetMetaData等等。代碼隻要寫一遍,所有的對象就都可以應用上了。
  3. **統一的記憶體配置設定釋放。**實際上Cocos2dx裡的CCObject的目的就是如此,可惜就是實作得不夠好而已。用引用計數方案的話,你可以在Object上添加Retain+1/Release-1的接口;用GC的方案,你也有了一個統一Object可以引用,是以這也是為何幾乎所有支援GC的語言都會設計出來一個Object基類的原因了。
  4. **統一的序列化模型。**如果想要讓系統裡的各種類型對象支援序列化,那麼你要嘛針對各種類型分别寫一套(如protobuf就是用程式生成了序列化代碼),要嘛就得利用模闆和宏各種标記識别(我自己Medusa引擎裡實作的序列化子產品Siren就是如此實作的),而如果有了一個Object基類,最差的我們就可以利用上繼承機制把統一的序列化代碼放到Object裡面去。而如果再加上設計良好的反射機制,實作序列化就更加的友善了。
  5. **統計功能。**比如說我們想統計看看整個程式跑下來,哪種對象配置設定了最多次,哪種對象配置設定的時間最長,哪種對象存活的時間最長。等等其他很便利的功能,在有了可追蹤和統一接口的基礎上,我們也能友善的實作出來。
  6. **調試的便利。**比如對于一塊洩漏了的記憶體資料,如果是多類型對象,你可能壓根沒法知道它是哪個對象。但是如果你知道它是Object基類下的一個子類對象,你可以把位址轉換為一個Object指針,然後就可以一目了然的檢視對象屬性了。
  7. **為反射提供便利。**如果沒有一個統一Object,你就很難為各種對象實作GetType接口,否則你就得在每個子類裡都定義實作一遍,用宏也隻是稍微緩解治标不治本。
  8. **UI編輯的便利。*和編輯器內建的時候,為了讓UI的屬性面闆控件能編輯各種對象。不光需要反射功能的支援,還需要引用一個統一Object指針。否則想象一下如果用一個void Object,你還得額外添加一個ObjectType枚舉用來轉換成正确類型的C++對象,而且隻能支援特定類型的C++類型對象。

代價:

  1. **臃腫的Object。**這算是繼承的祖傳老毛病了,我們越想為所有對象提供額外功能,我們就越會在Object裡堆積大量的函數接口和成員屬性。久而久之,這個Object身上就挂滿了各種代碼,可了解性就大大降低。Java和C#裡的Object比較簡單,看起來隻有個位數的接口,那是因為有C++在JVM和CLR的背後默默的幹着那些髒活累活,沒顯示出來給你看而已。而UE在原生的的C++基礎上開始搭建這麼一套系統,就是如今這麼一個重量級的UObject了,大幾十個接口,很少有人能全部掌握。
  2. **不必要的記憶體負擔。*有時候有些屬性并不是所有對象都用的到,但是因為不确定,為了所有對象在需要的時候就可以有,你還是不得不放在Object裡面。比如說一個最簡單的void UserData,看起來為所有對象附加一個void*資料也挺合理的,用的時候設定取出就好了。但是其實有些類型對象可能一輩子都用不到,用不到的屬性,卻還占用着記憶體,就是浪費。是以在一個統一的Object裡加資料,就得非常的克制,不然所有的對象都不得不得多一份占用。
  3. **多重繼承的限制。**比如C多重繼承于A和B,以前A和B都不是Object的時候還好,雖然大家對C++裡的多重繼承不太推薦使用,但是基本上也是不會有大的使用問題的。然後現在A和B都繼承于Object了,現在讓C想多重繼承于A和B,就得面臨一個尴尬的局面,變成菱形繼承了!而甭管用不用得上全部用虛繼承顯然也是不靠譜的。是以一般有object基類的程式設計語言,都是直接限制多重繼承,改為多重實作接口,避免了資料被繼承多份的問題。
  4. **類型系統的割裂。**除非是像java和C#那樣,對使用者隐藏整個背後系統,否則使用者在面對原生C++類型和Object類型時,就不得不去思考劃分對象類型。兩套系統在交叉引用、互相加載釋放、消息通信、記憶體配置設定時采用的機制和規則也是大不一樣的。哪些對象應該繼承于Object,哪些不用;哪些可以GC,哪些隻能用智能指針管理;C++對象裡new了Object對象該怎麼管理,Object對象裡new了C++對象什麼時候釋放?這些都是強加給使用者思考的問題。

繼續閱讀