新手,有错请指出,大家共同进步
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++对象什么时候释放?这些都是强加给用户思考的问题。