Skip to main content

🔤 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 const correctly 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

SituationUnreal convention
Passing a big struct you won't modifyconst FHitResult& — reference avoids a copy
Referring to another actor/UObjectAActor* — 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 inNon-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." The const protects 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:

graph LR H["MyActor.h
declarations: what exists"] --> U["Used by other headers"] C["MyActor.cpp
definitions: how it works"] --> B["Compiled into the module"] H --> C
Figure 1: The header declares the interface; the source defines the implementation. Other files include the header, not the source.

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 a UCLASS/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 .cpp where 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& parameters pass big data cheaply and safely; a trailing const on 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-CAPS U* 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.