新手,有錯請指出,大家共同進步
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的根基類設計到底有什麼深遠的影響,我們又付出了什麼代價?
得到:
- **萬物可追蹤。**有了一個統一基類Object,我們就可以根據一個object類型指針追蹤到所有的派生對象。如果願意,我們都可以把目前的所有對象都周遊出來。按照純面向對象的思想,萬物皆是對象,是以有一個基類Object會大大友善管理。如果再加上一些機制,我們甚至可以把系統中的所有對象的引用圖給展示出來。
- **通用的屬性和接口。**得益于繼承機制,我們可以在object裡加上我們想應用于所有對象的屬性和接口,包括但不限于:Equals、Clone、GetHashCode、ToString、GetName、GetMetaData等等。代碼隻要寫一遍,所有的對象就都可以應用上了。
- **統一的記憶體配置設定釋放。**實際上Cocos2dx裡的CCObject的目的就是如此,可惜就是實作得不夠好而已。用引用計數方案的話,你可以在Object上添加Retain+1/Release-1的接口;用GC的方案,你也有了一個統一Object可以引用,是以這也是為何幾乎所有支援GC的語言都會設計出來一個Object基類的原因了。
- **統一的序列化模型。**如果想要讓系統裡的各種類型對象支援序列化,那麼你要嘛針對各種類型分别寫一套(如protobuf就是用程式生成了序列化代碼),要嘛就得利用模闆和宏各種标記識别(我自己Medusa引擎裡實作的序列化子產品Siren就是如此實作的),而如果有了一個Object基類,最差的我們就可以利用上繼承機制把統一的序列化代碼放到Object裡面去。而如果再加上設計良好的反射機制,實作序列化就更加的友善了。
- **統計功能。**比如說我們想統計看看整個程式跑下來,哪種對象配置設定了最多次,哪種對象配置設定的時間最長,哪種對象存活的時間最長。等等其他很便利的功能,在有了可追蹤和統一接口的基礎上,我們也能友善的實作出來。
- **調試的便利。**比如對于一塊洩漏了的記憶體資料,如果是多類型對象,你可能壓根沒法知道它是哪個對象。但是如果你知道它是Object基類下的一個子類對象,你可以把位址轉換為一個Object指針,然後就可以一目了然的檢視對象屬性了。
- **為反射提供便利。**如果沒有一個統一Object,你就很難為各種對象實作GetType接口,否則你就得在每個子類裡都定義實作一遍,用宏也隻是稍微緩解治标不治本。
- **UI編輯的便利。*和編輯器內建的時候,為了讓UI的屬性面闆控件能編輯各種對象。不光需要反射功能的支援,還需要引用一個統一Object指針。否則想象一下如果用一個void Object,你還得額外添加一個ObjectType枚舉用來轉換成正确類型的C++對象,而且隻能支援特定類型的C++類型對象。
代價:
- **臃腫的Object。**這算是繼承的祖傳老毛病了,我們越想為所有對象提供額外功能,我們就越會在Object裡堆積大量的函數接口和成員屬性。久而久之,這個Object身上就挂滿了各種代碼,可了解性就大大降低。Java和C#裡的Object比較簡單,看起來隻有個位數的接口,那是因為有C++在JVM和CLR的背後默默的幹着那些髒活累活,沒顯示出來給你看而已。而UE在原生的的C++基礎上開始搭建這麼一套系統,就是如今這麼一個重量級的UObject了,大幾十個接口,很少有人能全部掌握。
- **不必要的記憶體負擔。*有時候有些屬性并不是所有對象都用的到,但是因為不确定,為了所有對象在需要的時候就可以有,你還是不得不放在Object裡面。比如說一個最簡單的void UserData,看起來為所有對象附加一個void*資料也挺合理的,用的時候設定取出就好了。但是其實有些類型對象可能一輩子都用不到,用不到的屬性,卻還占用着記憶體,就是浪費。是以在一個統一的Object裡加資料,就得非常的克制,不然所有的對象都不得不得多一份占用。
- **多重繼承的限制。**比如C多重繼承于A和B,以前A和B都不是Object的時候還好,雖然大家對C++裡的多重繼承不太推薦使用,但是基本上也是不會有大的使用問題的。然後現在A和B都繼承于Object了,現在讓C想多重繼承于A和B,就得面臨一個尴尬的局面,變成菱形繼承了!而甭管用不用得上全部用虛繼承顯然也是不靠譜的。是以一般有object基類的程式設計語言,都是直接限制多重繼承,改為多重實作接口,避免了資料被繼承多份的問題。
- **類型系統的割裂。**除非是像java和C#那樣,對使用者隐藏整個背後系統,否則使用者在面對原生C++類型和Object類型時,就不得不去思考劃分對象類型。兩套系統在交叉引用、互相加載釋放、消息通信、記憶體配置設定時采用的機制和規則也是大不一樣的。哪些對象應該繼承于Object,哪些不用;哪些可以GC,哪些隻能用智能指針管理;C++對象裡new了Object對象該怎麼管理,Object對象裡new了C++對象什麼時候釋放?這些都是強加給使用者思考的問題。