UE4 uses a rather special way of constructing objects. It’s been constantly changing over time, too, so documenting it is no small task.
The short version is that it follows the Prototype creational pattern. UObjects are not entirely created from scratch, but first initialized using a template object. This template object is called the Class Default Object (CDO) and as the name implies, every UClass has a default object.
On top of that, UObjects can be constructed by composition, through subobjects such as ActorComponents. When these subobjects also happen UObjects, they follow the same logic and have a class default object. But in order to support composition transparently, objects tend to include their subobjects in various processes like construction or serialization. Most of the time, these subobjects will not be shared across different parent objects (you wouldn’t want all your Characters to share the same mesh!). Each object creates its own instance of subobjects when necessary, leading to more construction.
So, what you’re seeing is CDOs and instances being initialised one after the other. In the case of the PlayerController, it’s roughly as follows:
- Construct PlayerController Class Default Object
- Construct PlayerController instance then copy CDO defualts
And for the component in your PlayerController:
- Construct MyActorComponent Class Default Object
- Construct MyActorComponent instance
- Construct MyActorComponent instance specific to previous PlayerController instance
And that’s the jist of it. This is a very rough summary of my own research and debugging.
You might wonder, why bother running the constructor again if you’re copying properties from object defaults? That’s something laden with history in the engine. It used to be that you had to surround parts of your constructor with a PCIP.ShouldInitializePropertiesToDefault() check in order to initialize properties and unmanaged members separately. This was all streamlined recently and all objects go through the full constructor with little to no caveats. So the construction part of the CDO becomes a bit redundant, but Class Default Objects are however still very necessary for specialized concepts like serialization and blueprints. And using this approach also means you can initialize different variants of the same class using “archetype” objects, so you can easily save data-driven object variants simply by storing an instance of this class and copying new ones from that archetype.
All of this complexity is compounded by the fact that Blueprints follow their own initialization paths, blueprint components are treated very differently from native components and other special contingencies in place just for blueprints. It’s a lot to take in, and even after working with UE4 for over a year, I’ve barely seen the tip of the iceberg. For the most part now, you can just leave the nitty gritty details to the engine without having to worry about it.