🔤 Lesson 1.4: A C++ Refresher, Unreal-Flavored
This isn't a full C++ course — it's a targeted refresher on the handful of language features Unreal leans on hardest, taught with Unreal-shaped examples so the later lessons never blindside you.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Distinguish references, pointers, and values, and know which Unreal uses where
- Apply
constcorrectly to parameters and member functions - Explain the header/source split and use forward declarations to keep includes lean
- Read the template and macro syntax (
TArray<T>,Cast<T>,UPROPERTY()) you'll meet everywhere
Estimated Time: 60 minutes
Engine Version: Unreal Engine 5.8
⚠️ Scope note
If terms like "class," "function," and "loop" are brand new, work through a general C++ primer first. This lesson assumes basic programming and focuses on the C++ that Unreal specifically demands.
In This Lesson
Values, References & Pointers
Unreal C++ is full of * and &. Getting these straight now saves endless confusion later, because Unreal has strong conventions about which to use.
The three ways to pass and hold data
int32 Health = 100; // a VALUE: Health owns its own int
int32& RefHealth = Health; // a REFERENCE: another name for Health (no copy, can't be null)
RefHealth = 80; // this changes Health to 80
int32* PtrHealth = &Health; // a POINTER: holds the address of Health; CAN be null
*PtrHealth = 50; // "dereference" to reach the value; sets Health to 50
PtrHealth = nullptr; // pointers can be reseated or nulled
📖 The distinction that matters
A reference is an alias — it always refers to a valid object and can't be reseated. A pointer holds an address, can be nullptr, and can be changed to point elsewhere. "Can this legitimately be nothing?" is usually what decides between them.
How Unreal uses each
| Situation | Unreal convention |
|---|---|
| Passing a big struct you won't modify | const FHitResult& — reference avoids a copy |
| Referring to another actor/UObject | AActor* — a pointer (it might be null or get destroyed) |
| Small values (int, float, bool, enum) | Pass by value — copying is cheap |
| An "out" parameter a function fills in | Non-const reference, e.g. FHitResult& OutHit |
⚠️ The null-pointer trap
Because Unreal objects are referenced by pointers and can be destroyed, always check a pointer before using it: if (Target) { Target->DoThing(); }. Dereferencing a null pointer crashes. We'll formalize safe-access patterns (and the IsValid() helper) in Module 2.
Const Correctness
Unreal code uses const heavily, and reading it fluently is a real skill. const is a promise to the compiler — and to other programmers — that something won't be modified.
// const parameter: this function promises not to change the passed struct
float GetDamageFrom(const FAttackData& Attack);
// const member function: promises not to modify the object it's called on
class AEnemy : public AActor
{
public:
// The trailing 'const' means: calling GetHealth() won't change this Enemy.
int32 GetHealth() const { return Health; }
// No trailing const: this one is allowed to modify the Enemy.
void TakeDamage(int32 Amount) { Health -= Amount; }
private:
int32 Health = 100;
};
✅ Why it's worth it
A const reference lets you pass large objects with zero copy while guaranteeing you won't accidentally mutate them. A const member function documents intent and lets the compiler catch mistakes. Unreal's own API is thoroughly const-correct, so matching it makes your code interoperate cleanly.
💡 Read it right-to-left:const FAttackData&is "a reference to a const FAttackData." Theconstprotects the data; the&avoids the copy. That exact combination is the single most common parameter form in the engine.
Headers & Forward Declarations
C++ splits a class across two files, and Unreal follows this rigorously:
declarations: what exists"] --> U["Used by other headers"] C["MyActor.cpp
definitions: how it works"] --> B["Compiled into the module"] H --> C
The forward-declaration habit
Including a header pulls in everything it includes — do that carelessly and compile times explode. Unreal's answer is the forward declaration: in a header, you often only need to say "this type exists," not include its full definition.
// MyActor.h
// Forward declarations — we only use these as pointers here, so we don't
// need their full headers yet. This keeps compile times down.
class UStaticMeshComponent;
class AEnemy;
#include "GameFramework/Actor.h"
#include "MyActor.generated.h" // ALWAYS the last include in a UCLASS header
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(VisibleAnywhere)
UStaticMeshComponent* Mesh; // pointer to a forward-declared type: fine
void ChaseTarget(AEnemy* Target);
};
// MyActor.cpp — here we DO need the full definitions, so we include them
#include "MyActor.h"
#include "Components/StaticMeshComponent.h" // now we can use its members
#include "Enemy.h"
void AMyActor::ChaseTarget(AEnemy* Target)
{
if (Target) { /* ... use Target's full API ... */ }
}
⚠️ Two rules you must not break
- The
#include "X.generated.h"line must be the last include in any header that declares aUCLASS/USTRUCT/UENUM. The header tool generates it. - Use forward declarations in headers when you only need a pointer or reference; include the full header in the
.cppwhere you actually call members. We revisit this discipline in Lesson 2.1.
Templates & Macros You'll See
You don't need to write templates to use Unreal, but you'll read them constantly. Two syntaxes to be comfortable with:
Templates: angle brackets mean "of what type"
TArray<int32> Scores; // a dynamic array OF int32
TArray<AActor*> Enemies; // a dynamic array OF actor pointers
TMap<FName, int32> Inventory; // a map from FName keys TO int32 values
// Cast<T> is a template too — safely converts a base pointer to a derived type,
// returning nullptr if the object isn't actually that type:
AEnemy* AsEnemy = Cast<AEnemy>(SomeActor);
if (AsEnemy) { /* it really was an AEnemy */ }
These are Unreal's own container and utility templates. We cover the containers in Lesson 2.4 and Cast in Module 2 — for now, just read TArray<AActor*> as "array of actor pointers" and move on.
Macros: the CAPS words are code generators
UCLASS() // marks a class for the reflection system
GENERATED_BODY() // expands into engine-generated boilerplate
UPROPERTY(...) // exposes a member variable to the engine/editor
UFUNCTION(...) // exposes a function to the engine/Blueprint
UE_LOG(...) // logging
📖 Why macros?
These uppercase tokens are macros processed by the Unreal Header Tool before the C++ compiler runs. They generate the reflection data that lets your class talk to the editor, Blueprint, serialization, and networking. You write a one-line macro; the tool writes hundreds of lines you never see. Module 2 pulls back this curtain.
💡 Don't panic at unfamiliar CAPS: when you see an ALL-CAPS token followed by parentheses at class or member scope, it's almost certainly a reflection macro. You'll learn each one in context.
Hands-on Exercise & Quiz
🏋️ Exercise: Read the code like Unreal
Objective: Translate signatures into plain English — the everyday skill of reading engine code.
Explain, in one sentence each, what these declarations promise:
// A
void ApplyDamage(const FDamageEvent& Event, AActor* Instigator);
// B
float GetSpeed() const;
// C
TArray<AActor*> FindEnemiesInRadius(float Radius) const;
✅ Answers
- A: Takes a damage event by const reference (no copy, won't modify it) and an instigator pointer that may be null; returns nothing.
- B: Returns the object's speed and promises not to modify the object (const member function).
- C: Returns a dynamic array of actor pointers and doesn't modify the object it's called on.
🎯 Quick Quiz
Question 1: Which should you use to refer to another actor that might be destroyed?
Question 2: What does the trailing const in int32 GetHealth() const; guarantee?
Question 3: Where must #include "MyActor.generated.h" appear?
Summary
🎉 Key Takeaways
- References alias a valid object (no copy, never null); pointers hold an address (nullable, reseatable). Unreal refers to objects with pointers and always null-checks them.
const FThing¶meters pass big data cheaply and safely; a trailingconston a method promises it won't modify the object.- Headers declare, sources define; use forward declarations in headers to keep includes and compile times lean.
- The
#include "X.generated.h"line is always the last include in a reflected header. - Read
TArray<T>/Cast<T>as "of type T," and treat ALL-CAPSU*tokens as reflection macros you'll learn in context.
📚 Additional Resources
🚀 What's Next?
Language ready, tools ready. Time for the moment it all pays off: creating your first C++ class in the editor, compiling it, and watching Live Coding turn edits into running behavior in seconds.
🎉 Lesson complete!
You can read Unreal C++. Now let's write some.