BPVM Snack Pack #5 - SuperStruct: Pointer-Based Inheritance
Blueprint classes don't use C++ inheritance. They use a pointer-based system through SuperStruct. Here's why that design matters.
The content in this post is based on Unreal Engine 5.4.0
BPVM Snack Pack - Quick Blueprint knowledge drops! Part of the Blueprint to Bytecode series.
C++ Inheritance vs Blueprint Inheritance
When you create a Blueprint from AMyActor, the editor says you’re creating a subclass of AMyActor.
That’s true from an API perspective - it behaves like a subclass. But the implementation is completely different from C++ inheritance.
What True C++ Inheritance Looks Like
1
2
3
4
5
6
7
// Real C++ inheritance
class AMyChildActor : public AMyActor // ✅ True inheritance
{
// Compiler creates vtable
// Memory layout includes parent's data
// Linker resolves function addresses
};
With true inheritance:
- The compiler bakes the relationship into the binary at compile time
- The vtable is statically linked
- The memory layout includes all parent members
- Everything is resolved statically (fast, but inflexible)
What Blueprint “Inheritance” Actually Is
1
2
3
4
5
6
7
8
// Blueprint's approach
class UBlueprintGeneratedClass : public UClass // NOT AMyActor!
{
// This is a UClass, not your actor!
};
// Somewhere during compilation:
GeneratedClass->SetSuperStruct(AMyActor::StaticClass());
Here’s the key insight:
UBlueprintGeneratedClassinherits fromUClass(not your actor!)- It stores a pointer to the parent via
SetSuperStruct() - When you call
GetSuperClass(), it follows that pointer
This is composition + delegation, not traditional inheritance.
The Pointer Chain
Here’s the actual relationship:
1
2
3
4
5
6
7
8
9
10
11
12
UBlueprintGeneratedClass* GeneratedClass;
// |
// | SetSuperStruct()
// v
UClass* ParentClass = AMyActor::StaticClass();
// |
// | GetSuperClass()
// v
UClass* GrandParent = AActor::StaticClass();
// |
// v
UObject::StaticClass();
It’s a linked list of pointers, not C++ inheritance!
Why This Matters
Problem 1: Property Lookup
When you access a variable on a Blueprint instance:
1
2
// BP_MyActor has variable "Health"
float MyHealth = MyActor->Health;
Under the hood:
- Look for
HealthinGeneratedClassproperties - Not found? Follow
SuperStructpointer to parent - Repeat until found or reach
UObject
This is runtime reflection, not compile-time!
Problem 2: Function Calls
When you call a function:
1
MyActor->Foo();
The engine:
- Checks if
GeneratedClassoverridesFoo - If not, follows
SuperStructchain - Finds the function in parent class
- Executes (could be bytecode OR native C++)
Again, runtime lookup!
The Benefits
Why use pointers instead of real inheritance?
1. Hot Reloading
1
2
3
4
5
6
7
8
// Recompile Blueprint while game is running
GeneratedClass->CleanAndSanitize(); // Clear old data
Compile(Blueprint); // Fill with new data
Reinstancer->UpdateInstances(); // Update existing objects
// Still using the SAME GeneratedClass object!
// No memory address changes (sort of...)
// No pointer fixups needed
2. Dynamic Class Creation
1
2
3
4
// Create Blueprint classes at runtime!
UBlueprint* NewBP = CreateBlueprint(...);
Compile(NewBP);
// Now you have a new "class"
3. Circular Dependencies
1
2
3
4
5
BP_A->SetSuperStruct(BP_B); // A "inherits" from B
BP_B->SetSuperStruct(BP_A); // ERROR: Would create cycle!
// But the pointer system can detect this
// And create skeleton classes as intermediaries
The Trade-Off
C++ Inheritance (Fast):
1
2
3
class Child : public Parent { };
// Compile time: vtable, memory layout
// Runtime: Direct memory access, no lookup
Blueprint SuperStruct (Flexible):
1
2
3
Generated->SetSuperStruct(Parent);
// Compile time: Nothing baked in
// Runtime: Pointer chasing, reflection lookup
Blueprint trades performance for flexibility - classic game dev trade-off.
How to Think About It
Bad mental model:
1
BP_MyActor : public AMyActor // ❌ Not what's happening
Good mental model:
1
2
3
4
5
6
class BP_MyActor {
UClass* Parent = AMyActor::StaticClass(); // ✅ Pointer relationship
TArray<FProperty*> MyProperties;
TArray<UFunction*> MyFunctions;
TArray<uint8> Bytecode;
};
Quick Takeaway
- Blueprint classes DON’T use C++ inheritance
- They use
SetSuperStruct()/GetSuperClass()(pointer chain) - This enables hot reloading and runtime class creation
- Trade-off: More flexible, but slower than C++ inheritance
- The reflection system makes it look like inheritance to developers
The Abstraction Works
From your Blueprint code, it behaves exactly like inheritance:
1
2
3
// In your Blueprint, this just works
Parent::MyFunction(); // Calls parent version
Super::Tick(); // Calls parent tick
But under the hood, it’s all pointer chasing and reflection lookups. The abstraction is so good that most developers never need to know the difference.
Want More Details?
For the complete explanation with code:
Next snack: The mysterious Class Default Object (CDO)!
🍿 BPVM Snack Pack Series
- ← #4: Skeleton Classes
- #5: The SuperStruct Magic Trick ← You are here
- #6: The CDO Mystery →