📝 Lesson 21: C++ Prerequisites for Unreal Engine
Bridge the gap between core C++ and Unreal Engine's C++ dialect — the classes, pointers, memory model, containers, delegates, and macros you'll lean on from day one.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Identify which core C++ topics (classes, inheritance, pointers, templates) are critical, important, or merely helpful for Unreal Engine work.
- Explain why UObject pointers must be marked
UPROPERTY()to survive Unreal's garbage collector, and when to reach forTWeakObjectPtrinstead. - Translate familiar STL containers and strings (
std::vector,std::string, smart pointers) to their Unreal equivalents (TArray,FString,TSharedPtr/TUniquePtr). - Use Unreal delegates and lambdas for event handling, and recognize the essential
UCLASS/UPROPERTY/UFUNCTIONmacros. - Handle errors the Unreal way — validity checks and assertion macros — instead of exceptions.
Estimated Time: 100–130 minutes
Project: Refactor the standard C++ class in this lesson's practice exercise into idiomatic Unreal Engine C++ using UCLASS, UPROPERTY, TArray, and IsValid() checks.
In This Lesson
The C++ Knowledge Map for Unreal
Think of Unreal Engine as a massive spaceship. You don't need to understand every circuit to fly it, but you do need to know the essential controls. This guide shows you exactly which C++ concepts are critical for Unreal Engine development.
Classes and Objects: The Foundation
Unreal Engine is built entirely on object-oriented programming. Every actor, component, and system is a class. You'll be creating and extending classes constantly.
What You Must Know
// 1. Basic Class Structure - CRITICAL for Unreal
class GameCharacter {
private:
float health; // Unreal uses float extensively
FString name; // Unreal's string type
protected:
int32 level; // Unreal's int32 typedef
public:
// Constructor - Called when object is created
GameCharacter() : health(100.0f), level(1) {
// Initialization
}
// Destructor - Called when object is destroyed
virtual ~GameCharacter() {
// Cleanup - Unreal handles most cleanup automatically
}
// Getter/Setter pattern used everywhere in Unreal
float GetHealth() const { return health; }
void SetHealth(float newHealth) { health = newHealth; }
// Virtual functions for polymorphism
virtual void TakeDamage(float damage) {
health -= damage;
}
};
// 2. Inheritance - CRITICAL for extending Unreal classes
class PlayerCharacter : public GameCharacter {
private:
int32 experience;
public:
PlayerCharacter() : GameCharacter(), experience(0) {
// Call parent constructor
}
// Override parent function
virtual void TakeDamage(float damage) override {
// Custom player damage logic
float reducedDamage = damage * 0.8f; // Players take less damage
GameCharacter::TakeDamage(reducedDamage);
}
// New functions specific to player
void GainExperience(int32 amount) {
experience += amount;
CheckLevelUp();
}
private:
void CheckLevelUp();
};
// 3. Abstract Classes/Interfaces - Used for Unreal interfaces
class IDamageable {
public:
virtual ~IDamageable() = default;
virtual void TakeDamage(float damage) = 0; // Pure virtual
virtual bool IsDead() const = 0;
};
Pointers and References: Memory Navigation
Unreal Engine uses pointers everywhere. You'll be constantly working with pointers to Actors, Components, and other game objects. Understanding pointers is non-negotiable.
Essential Pointer Knowledge
// 1. Basic Pointer Operations - MUST KNOW
void PointerBasics() {
// Creating pointers
AActor* ActorPtr = nullptr; // Always initialize to nullptr
// Getting pointers from Unreal
ActorPtr = GetWorld()->SpawnActor<AActor>(ActorClass);
// Checking validity - CRITICAL IN UNREAL
if (ActorPtr != nullptr) {
// Safe to use
ActorPtr->SetActorLocation(FVector(0, 0, 100));
}
// Better validity check
if (IsValid(ActorPtr)) {
// Checks both nullptr and pending kill
ActorPtr->Destroy();
}
}
// 2. References - Used for function parameters
void ProcessCharacter(const ACharacter& Character) {
// Can't be null, always valid
float Health = Character.GetHealth();
}
// 3. Pointer vs Reference Usage in Unreal
class UMyComponent : public UActorComponent {
private:
// WRONG - Never store references as members
// AActor& OwnerRef; // This will cause problems!
// CORRECT - Use pointers with UPROPERTY
UPROPERTY()
AActor* Owner;
// CORRECT - Weak pointer for optional references
TWeakObjectPtr<AActor> Target;
public:
// References are fine for parameters
void Initialize(AActor& InOwner) {
Owner = &InOwner; // Convert to pointer for storage
}
// Const references for efficiency
void ProcessData(const FVector& Location) {
// No copy made, efficient
}
};
// 4. Smart Pointers in Unreal (Different from std::)
void UnrealSmartPointers() {
// TSharedPtr - Reference counted (not for UObjects!)
TSharedPtr<FMyStruct> SharedData = MakeShared<FMyStruct>();
// TUniquePtr - Single owner (not for UObjects!)
TUniquePtr<FRenderData> RenderData = MakeUnique<FRenderData>();
// TWeakObjectPtr - For UObjects
TWeakObjectPtr<AActor> WeakActor = SomeActor;
if (WeakActor.IsValid()) {
WeakActor->DoSomething();
}
}
Memory Management: Unreal's Garbage Collection
Unlike standard C++, Unreal Engine manages memory for you through garbage collection. Understanding how this works is crucial to avoid crashes and memory leaks.
Memory Rules You Must Follow
// 1. UObject Memory Management - CRITICAL
UCLASS()
class MYPROJECT_API AMyActor : public AActor {
GENERATED_BODY()
private:
// CORRECT - UPROPERTY protects from GC
UPROPERTY()
UStaticMeshComponent* MeshComponent;
// WRONG - Will be garbage collected!
UStaticMeshComponent* UnprotectedComponent;
// CORRECT - For non-UObjects
TUniquePtr<FMyStruct> StructData;
public:
AMyActor() {
// CORRECT - Let Unreal manage UObject memory
MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
// WRONG - Never use new/delete with UObjects!
// UnprotectedComponent = new UStaticMeshComponent(); // NEVER DO THIS!
// CORRECT - For non-UObjects
StructData = MakeUnique<FMyStruct>();
}
void SpawnExample() {
// CORRECT - Spawn actors through the world
AActor* NewActor = GetWorld()->SpawnActor<AActor>(ActorClass);
// WRONG - Never create actors with new
// AActor* BadActor = new AActor(); // CRASH!
}
~AMyActor() {
// Don't delete UObject pointers!
// MeshComponent will be handled by GC
// StructData will be automatically deleted (unique_ptr)
}
};
// 2. Preventing Garbage Collection
// Method 1 & 3 live on a class — UPROPERTY() is only valid on class members,
// never inside a function body.
UCLASS()
class MYPROJECT_API UMyManager : public UObject {
GENERATED_BODY()
// Method 1: UPROPERTY on a single UObject pointer
UPROPERTY()
AActor* ProtectedActor;
// Method 3: Strong references in a UPROPERTY container
UPROPERTY()
TArray<AActor*> ActorArray; // All actors in the array are protected
};
// Method 2: Add to the root set (rare, special cases) — this one is fine in a function
void PreventGC() {
UObject* ImportantObject = NewObject<UObject>();
ImportantObject->AddToRoot();
// Remember to remove: ImportantObject->RemoveFromRoot();
}
// 3. Working with Non-UObject Memory
struct FGameData {
TArray<float> Values;
FString Name;
// Unreal containers manage their own memory
void AddValue(float Val) {
Values.Add(Val); // Automatic resize
}
};
// 4. Memory Best Practices
class UMyGameSystem : public UObject {
private:
// Use Unreal's containers
TArray<int32> Scores; // Dynamic array
TMap<FString, float> PlayerStats; // Hash map
TSet<AActor*> TrackedActors; // Hash set
public:
void MemoryEfficientOperations() {
// Pre-allocate for performance
Scores.Reserve(100);
// Move semantics work in Unreal
TArray<int32> TempArray;
Scores = MoveTemp(TempArray);
// Shrink when done
Scores.Shrink();
}
};
Templates: Understanding Unreal's Generic Code
While you don't need to be a template wizard, you must understand basic templates because Unreal uses them extensively in containers and smart pointers.
Template Essentials for Unreal
// 1. Using Unreal's Template Containers - MUST KNOW
void ContainerBasics() {
// TArray - Dynamic array (like std::vector)
TArray<AActor*> Enemies;
Enemies.Add(Enemy1);
Enemies.Remove(Enemy2);
for (AActor* Enemy : Enemies) {
// Process each enemy
}
// TMap - Hash map (like std::unordered_map)
TMap<FString, int32> PlayerScores;
PlayerScores.Add("Player1", 100);
int32* Score = PlayerScores.Find("Player1");
// TSet - Hash set
TSet<FName> UniqueNames;
UniqueNames.Add("ItemName");
}
// 2. Template Functions You'll Use - MUST KNOW
void TemplateFunctions() {
AActor* Actor = GetSomeActor();
// Cast<T> - Safe casting with nullptr on failure
ACharacter* Character = Cast<ACharacter>(Actor);
if (Character) {
// Successfully cast to ACharacter
}
// FindComponentByClass<T> - Get component by type
UHealthComponent* Health = Actor->FindComponentByClass<UHealthComponent>();
// SpawnActor<T> - Spawn actors of specific type
AProjectile* Projectile = GetWorld()->SpawnActor<AProjectile>(
ProjectileClass, Location, Rotation
);
}
// 3. TSubclassOf - Class type safety - IMPORTANT
class AWeaponPickup : public AActor {
// This ensures WeaponClass is a subclass of AWeapon
UPROPERTY(EditDefaultsOnly)
TSubclassOf<AWeapon> WeaponClass;
void SpawnWeapon() {
if (WeaponClass) {
GetWorld()->SpawnActor<AWeapon>(WeaponClass);
}
}
};
// 4. Creating Simple Templates - NICE TO KNOW
template<typename T>
T* FindActorOfType(UWorld* World) {
for (TActorIterator<T> It(World); It; ++It) {
return *It; // Return first found
}
return nullptr;
}
// Usage
APlayerController* PC = FindActorOfType<APlayerController>(GetWorld());
Function Pointers and Delegates
Unreal uses delegates extensively for event handling. They're like function pointers on steroids, allowing flexible event systems.
Delegates and Lambdas
// 1. Basic Delegates - IMPORTANT for Unreal
// Declare delegate types
DECLARE_DELEGATE_OneParam(FOnHealthChanged, float);
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnDamageDealt, AActor*, float);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPlayerDied, ACharacter*, Character);
class AGameCharacter : public ACharacter {
public:
// Single delegate
FOnHealthChanged OnHealthChanged;
// Multicast - multiple listeners
FOnDamageDealt OnDamageDealt;
// Dynamic - Blueprint compatible
UPROPERTY(BlueprintAssignable)
FOnPlayerDied OnPlayerDied;
void TakeDamage(float Damage) {
Health -= Damage;
// Execute delegates
OnHealthChanged.ExecuteIfBound(Health);
OnDamageDealt.Broadcast(this, Damage);
if (Health <= 0) {
OnPlayerDied.Broadcast(this);
}
}
};
// 2. Using Lambdas - VERY COMMON in Unreal
void LambdaExamples() {
// Timer with lambda
FTimerHandle TimerHandle;
GetWorld()->GetTimerManager().SetTimer(TimerHandle, [this]() {
// This code runs after 2 seconds
UE_LOG(LogTemp, Warning, TEXT("Timer fired!"));
}, 2.0f, false);
// Binding to delegates with lambdas
Character->OnHealthChanged.BindLambda([](float NewHealth) {
UE_LOG(LogTemp, Warning, TEXT("Health: %f"), NewHealth);
});
// Array operations with lambdas
TArray<AActor*> Actors;
Actors.Sort([](const AActor& A, const AActor& B) {
return A.GetName() < B.GetName();
});
// Finding with predicates
AActor** Found = Actors.FindByPredicate([](AActor* Actor) {
return Actor->GetName() == "TargetActor";
});
}
// 3. Function Binding - COMMON pattern
class UMyWidget : public UUserWidget {
UFUNCTION()
void OnButtonClicked() {
// Handle click
}
void SetupWidget() {
if (MyButton) {
// Bind member function to button click
MyButton->OnClicked.AddDynamic(this, &UMyWidget::OnButtonClicked);
}
}
};
Macros and Preprocessor
Unreal Engine uses macros extensively. You don't need to create complex macros, but you must understand how to use Unreal's existing ones.
Macro Usage Guide
// 1. Essential Class Declaration Macros - MUST KNOW
UCLASS() // Makes class visible to Unreal
class MYPROJECT_API AMyActor : public AActor {
GENERATED_BODY() // Required boilerplate
// UPROPERTY macros for reflection
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health = 100.0f;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
UStaticMeshComponent* MeshComponent;
// UFUNCTION macros for Blueprint/reflection
UFUNCTION(BlueprintCallable, Category = "Actions")
void PerformAction();
UFUNCTION(BlueprintImplementableEvent, Category = "Events")
void OnActionCompleted(); // Implemented in Blueprint
};
// 2. Logging Macros - ESSENTIAL for debugging
void LoggingExamples() {
// Basic logging
UE_LOG(LogTemp, Warning, TEXT("Simple message"));
// With variables
int32 Score = 100;
FString PlayerName = "John";
UE_LOG(LogTemp, Display, TEXT("Player %s scored %d"), *PlayerName, Score);
// Different log levels
UE_LOG(LogTemp, Display, TEXT("Info message")); // Gray
UE_LOG(LogTemp, Warning, TEXT("Warning message")); // Yellow
UE_LOG(LogTemp, Error, TEXT("Error message")); // Red
}
// 3. Assertion Macros - IMPORTANT for debugging
void AssertionExamples() {
// check - Halts execution in non-shipping builds
check(Actor != nullptr);
check(Health > 0);
// ensure - Logs error but continues
ensure(Component != nullptr);
// verify - Like check but evaluates in all builds
verify(ProcessData());
}
// 4. Platform Macros - USEFUL
void PlatformSpecific() {
#if PLATFORM_WINDOWS
// Windows-specific code
#elif PLATFORM_MAC
// Mac-specific code
#elif PLATFORM_LINUX
// Linux-specific code
#endif
#if WITH_EDITOR
// Editor-only code
#endif
}
STL vs Unreal Containers
Unreal has its own container types. While STL knowledge helps, you'll use Unreal's containers exclusively in engine code.
// Container Comparison Reference
// STL → Unreal Equivalent
// std::vector → TArray
// std::unordered_map → TMap
// std::unordered_set → TSet
// std::string → FString
// std::shared_ptr → TSharedPtr (not for UObjects!)
// std::unique_ptr → TUniquePtr (not for UObjects!)
// std::function → TFunction / TDelegate
// Unreal Container Examples - MUST KNOW
void UnrealContainers() {
// TArray - Dynamic array
TArray<int32> Numbers;
Numbers.Add(42);
Numbers.AddUnique(42); // Won't add duplicate
Numbers.Remove(42);
Numbers.Empty(); // Clear array
// Useful TArray operations
Numbers.Reserve(100); // Pre-allocate
Numbers.Sort();
Numbers.Reverse();
int32 Index = Numbers.Find(42);
bool bContains = Numbers.Contains(42);
// TMap - Key-value pairs
TMap<FString, AActor*> ActorMap;
ActorMap.Add("Player", PlayerActor);
AActor** FoundActor = ActorMap.Find("Player");
ActorMap.Remove("Player");
// Iterating TMap
for (const auto& Pair : ActorMap) {
FString Key = Pair.Key;
AActor* Value = Pair.Value;
}
// TSet - Unique elements
TSet<FName> Tags;
Tags.Add("Enemy");
bool bHasTag = Tags.Contains("Enemy");
}
// String Types - CRITICAL
void UnrealStrings() {
// FString - Mutable string
FString MyString = TEXT("Hello");
MyString += TEXT(" World");
// FName - Immutable, case-insensitive identifier
FName TagName("EnemyTag");
// FText - Localized display text
FText DisplayText = FText::FromString("Display This");
// Conversions
FString StringFromName = TagName.ToString();
FName NameFromString(*MyString);
const TCHAR* CString = *MyString; // Get C-string
}
Error Handling: Unreal Style
Unreal doesn't use exceptions. Instead, it uses return codes, validity checks, and assertions.
// 1. No Exceptions! - CRITICAL
// WRONG - Unreal doesn't use exceptions
void WrongWay() {
try {
// Don't do this in Unreal!
throw std::runtime_error("Error");
} catch (...) {
// Unreal is compiled with exceptions disabled
}
}
// CORRECT - Unreal error handling
bool CorrectWay(AActor*& OutActor) {
OutActor = FindActor();
if (!IsValid(OutActor)) {
UE_LOG(LogTemp, Error, TEXT("Failed to find actor"));
return false;
}
return true;
}
// 2. Validity Checking - ESSENTIAL
void ValidityChecks() {
// Basic null check
if (Actor != nullptr) {
// Safe to use
}
// Better - checks null and pending kill
if (IsValid(Actor)) {
// Actor is truly valid
}
// For weak pointers
if (WeakActor.IsValid()) {
AActor* Actor = WeakActor.Get();
}
}
// 3. Assertions for Development - IMPORTANT
void DevelopmentAssertions() {
// check - Only in non-shipping builds
check(Component != nullptr);
checkf(Health > 0, TEXT("Health was %f"), Health);
// ensure - Logs and continues
if (!ensure(Data != nullptr)) {
return; // Handle gracefully
}
// Conditional checks
checkNoEntry(); // Should never reach this code
checkNoReentry(); // Prevent recursive calls
}
Quick Reference: C++ to Unreal
Pre-Flight Checklist
Before starting with Unreal Engine, make sure you can:
- ✓ Create classes with inheritance and virtual functions
- ✓ Use pointers and references correctly
- ✓ Understand when objects are created and destroyed
- ✓ Use template containers (even if you can't write complex templates)
- ✓ Work with header (.h) and source (.cpp) files
- ✓ Understand const correctness
- ✓ Use lambda functions for simple callbacks
- ✓ Read and understand basic preprocessor directives
Remember: You don't need to be a C++ expert to start with Unreal. Focus on the fundamentals, and learn advanced features as you need them. Unreal's systems will guide you toward good practices!
Practice Exercise: Unreal-Ready Code
🏋️ Challenge: Convert Standard C++ to Unreal Style
Take this standard C++ code and convert it to Unreal Engine style:
// Standard C++ Version
#include <vector>
#include <string>
#include <memory>
class GameObject {
private:
std::string name;
float health;
std::vector<GameObject*> children;
public:
GameObject(const std::string& n) : name(n), health(100.0f) {}
void TakeDamage(float damage) {
health -= damage;
if (health <= 0) {
delete this; // Dangerous!
}
}
void AddChild(GameObject* child) {
children.push_back(child);
}
};
// Your Task: Convert to Unreal style
// Hints:
// - Inherit from AActor
// - Use UCLASS() and GENERATED_BODY()
// - Replace std::string with FString
// - Replace std::vector with TArray
// - Add UPROPERTY macros
// - Remove manual delete
// - Add proper null checks
🎯 Quick Quiz
Question 1: Why must a raw UObject pointer stored as a class member be marked with UPROPERTY() in Unreal Engine?
Question 2: Which pairing correctly matches a standard C++ STL type with its Unreal Engine equivalent?
Question 3: Unreal Engine is typically compiled with C++ exceptions disabled. How does idiomatic Unreal code signal and handle failure instead?
Summary
🎉 Key Takeaways
- Classes, inheritance, and virtual functions are non-negotiable — every Actor and Component you touch is built on them.
- Pointers are everywhere in Unreal; always check validity with
IsValid()before use. UPROPERTY()protects UObject pointers from garbage collection — forgetting it is a common source of crashes.TWeakObjectPtris for optional references that shouldn't keep an object alive.- Unreal's containers (
TArray,TMap,TSet) and strings (FString,FName,FText) replace their STL counterparts in engine code. - Delegates and lambdas power Unreal's event system —
DECLARE_DELEGATE,Broadcast(), andBindLambda()are core patterns. - Macros like
UCLASS(),UPROPERTY(),UFUNCTION(), andGENERATED_BODY()drive Unreal's reflection system. - Exceptions are disabled — use return values,
IsValid(), andcheck()/ensure()for error handling instead.
📚 Additional Resources
🚀 What's Next?
You now know exactly which C++ fundamentals matter for game development and why. In Lesson 22: C++ and Unreal Engine Development, you'll put this knowledge to work — setting up an Unreal project, writing your first AActor subclass, and seeing UCLASS, UPROPERTY, and UFUNCTION in action inside a real engine.
🎉 You're Unreal-ready!
You've mapped the exact C++ skills Unreal Engine expects of you. That map is the hardest part — the engine work from here builds directly on it.