前置概念
Class Default Object与构造桥接函数
UE为每个UClass创建一个类默认对象Class Default Object,并且只有一个。创建普通实例时,UE通常以该类的CDO或显示指定的Archetype作为默认数据来源,通过FProperty属性链初始化反射属性,并根据需要实例化默认子对象。
UHT为每个UClass生成一个“构造桥接函数”,并且由ClassConstructor成员保存该函数指针。
例如:
UCLASS()
class AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor(const FObjectInitializer& ObjectInitializer);
};
UHT会生成一个隐藏的静态函数:
static void __DefaultConstructor(const FObjectInitializer& X)
{
// placement new:不分配内存,在现有地址上构造对象
new((void*)X.GetObj()) AMyActor(X);
}
这个函数的作用只有一个:
在已分配内存的前提下,把this指针套进构造函数体执行一次。基于C++的placement new语法,在已分配的内存上建立完整的C++对象生命周期,包括基类构造、构造函数体和成员初始化。
然后将静态函数赋予构造函数指针:
MyActor_UClass->ClassConstructor = &__DefaultConstructor;
CDO、Archetype、Template的区别
CDO:每个UClass的默认对象。
Template:创建对象时显示传入的模板,最终可能成为ObjectArchetype。
Archetype:对象初始化时参考的模板对象。通常时CDO,但不一定。例如:调用NewObject时指定了Template参数,Template对象将作为本次构造的ObjectArchetype。
NewObject()创建UObject对象
大致顺序为:
StaticConstructObject_Internal() -> StaticAllocateObject()
├─ 通过placement new在分配的内存上调用UObjectBase构造函数
└─ UObjectBase构造函数内调用AddObject()向全局对象表注册自身(GUObjectArray.AllocateUObjectIndex())
↓
创建 FObjectInitializer 临时对象
↓
构造桥接函数ClassConstructor,并传入FObjectInitializer
↓
ClassConstructor内通过placement new调用目标类型C++构造函数
↓
C++构造函数(父类构造函数/当前类构造函数)
↓
FObjectInitializer 析构
↓
FObjectInitializer::PostConstructInit()
├─ 属性初始化
├─ Config 加载
├─ 子对象属性初始化
└─ 子对象实例化
StaticAllocateObject()
如果传入的InName == NAME_None,则先生成唯一名称;如果显示提供名称,则尝试查找相同Outer + Name的已有对象。
如果不存在已有对象:
- 调用
GUObjectAllocator.AllocateUObject()分配全新内存
否则:
- 在已有对象地址上重构:
- 可复用的
DefaultSubobject- 不销毁
- 不清零
- 不执行
ClassConstructor - 只更新Flags等状态
- 其他对象
- 复用Linker、Flags、Index、SerialNumber
- 调用
ConditionalBeginDestroy() - 等待
IsReadyForFinishDestroy - 调用
ConditionalFinishDestroy()销毁对象。 - 调用
GUObjectArray.FreeUObjectIndex释放全局数组中的槽位 - 调用析构函数
Obj->~UObject() - 清理内存
FMemory::Memzero((void *)Obj, TotalSize)并调用构造函数new ((void *)Obj) UObjectBase() UObjectBase构造函数内调用GUObjectArray.AllocateUObjectIndex()重新向全局对象表注册自身- 重新设置Linker。
- 可复用的
调用UHT生成的构造桥接函数,建立FObjectInitializer临时对象
通过placement new对已有内存上建立对象生命周期并执行:
- 基类构造
- 成员初始化
- 构造函数体
- 编译器需要的vptr设置
UE中代码:
(*InClass->ClassConstructor)(FObjectInitializer(…))
接下来就是执行对象的C++构造函数体内的代码,比如:调用CreateDefaultSubobject()创建组件对象。
注意:如果上一步
StaticAllocateObject()返回bRecycledSubobject为true,表示当前正在创建的是子对象,则不会执行构造桥接函数。
FObjectInitializer析构函数
FObjectInitializer为C++构造函数执行期间提供当前对象、Archetype、默认子对象覆盖等上下文。
当前面的C++构造函数体执行完毕后,传入的FObjectInitializer对象就要退出作用域并执行析构函数了。
析构函数~FObjectInitializer负责在C++构造函数后完成UObject的创建。
如果当前对象的类型不是
UObject基类本身,且没有显示提供ObjectArchetype,则尝试调用Class->GetDefaultObject()获取CDO或创建CDO作为ObjectArchetype。
创建Class Default Object
Class->GetDefaultObject()内最终调用到UClass::CreateDefaultObject()执行创建CDO的具体过程:
- 同样调用
StaticAllocateObject()分配内存,但会附带标记RF_ClassDefaultObject和RF_ArchetypeObject,表示当前创建的是CDO或模板对象。 - 同样通过调用构造桥接函数
(*ClassConstructor)(FObjectInitializer())建立对象生命周期。 - 调用CDO对象的
PostCDOContruct()。
CDO也通过
FObjectInitializer完成收尾初始化,但它通常以父类CDO作为继承属性来源;普通实例则通常以当前类CDO或指定的Template作为默认数据来源。
接下来析构函数会调用PostConstructInit()进行对象的初始化工作。
FObjectInitializer::PostConstructInit()
PostConstructInit()函数负责完成UE对象系统层面的初始化:
- 根据
ObjectArchetype或CDO初始化属性。 - 按需要加载Config。即
Obj->LoadConfig()。 - 实例化子对象
InstanceSubobjects()。 - 调用对象和类型相关的生命周期函数或回调函数。例如:
Obj->PostInitProperties()、Class->PostInitInstance()。
NewObject与对象加载的区别
从
Package加载对象时,构造流程之后还会由序列化数据覆盖部分默认属性,并调用PostLoad();因此加载对象的最终状态不能只通过构造函数和CDO推导。
UWorld中生成Actor
在UWorld创建一个Actor的底层也是要调用NewObject(),只不过在UObject基础上增加了组件注册、BeginPlay()等Actor生命周期。
引擎源码概念流程如下:
AActor* UWorld::SpawnActor(
UClass* Class,
FTransform const* UserTransformPtr,
const FActorSpawnParameters& SpawnParameters
)
{
// Engine/Private/LevelActor.cpp
// 以下为简化流程,并非可直接编译的完整源码
//调用NewObject创建对象
AActor* const Actor = NewObject<AActor>(...);
// ─────────────────────────────
// ⑤ 注册到世界(但还没 BeginPlay)
// ─────────────────────────────
LevelToSpawnIn->TryAddActorToList( Actor, /*bAddUnique*/false);
// ─────────────────────────────
// ⑥ Actor 特有初始化
// ─────────────────────────────
Actor->PostSpawnInitialize()
{
Actor->RegisterAllComponents()//注册组件
Actor->PostActorCreated()//调用生命周期函数
if(不是Deferred Spawn)
{
Actor->FinishSpawning()//Spawn完毕
{
Actor->ExecuteConstruction(FinalRootComponentTransform,...)//设置Transform
Actor->PostActorConstruction()//主要是初始化组件
World->OnActorFinishedSpawning(this)//通知UWorld
}
}
}
return Actor;
}

Discussion
留言讨论
发表留言时,评论服务会记录必要的 IP 与 User-Agent 信息,用于安全审核和防止滥用。