天天看点

虚幻四源码阅读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++对象什么时候释放?这些都是强加给用户思考的问题。

继续阅读