Intermediate 12 min

Pointers vs References

Learn the key differences between pointers and references, and when to use each in professional C++ code

Understand when to use pointers vs references to write safer, cleaner C++ code.

A Simple Example

#include <iostream>
#include <string>

class Player {
public:
    std::string name;
    int health;

    Player(std::string n, int h) : name{n}, health{h} {}
};

// Using references - safer, cleaner
void healByReference(Player& player, int amount) {
    player.health += amount;
    // Can't be null, can't reassign to different player
}

// Using pointers - more flexible
void healByPointer(Player* player, int amount) {
    if (player != nullptr) {  // Must check for null!
        player->health += amount;
    }
}

// Pointers allow rebinding
void demonstrateRebinding() {
    Player alice{"Alice", 100};
    Player bob{"Bob", 80};

    Player* ptr{&alice};
    std::cout << ptr->name << "\n";  // Alice

    ptr = &bob;  // Can point to different player
    std::cout << ptr->name << "\n";  // Bob

    // References can't do this:
    Player& ref{alice};
    // ref = bob;  // This assigns bob's data to alice, doesn't rebind!
}

int main() {
    Player hero{"Hero", 100};

    healByReference(hero, 20);
    std::cout << "After reference heal: " << hero.health << "\n";

    healByPointer(&hero, 15);
    std::cout << "After pointer heal: " << hero.health << "\n";

    healByPointer(nullptr, 10);  // Safe - function handles null

    demonstrateRebinding();

    return 0;
}

Breaking It Down

References: Safe Aliases

  • Must be initialized: int& ref{x}; - can't create uninitialized references
  • Can't be null: References always refer to a valid object
  • Can't be rebound: Once bound to an object, always refers to that object
  • Remember: References are safer but less flexible than pointers

Pointers: Flexible but Risky

  • Can be null: int* ptr{nullptr}; is valid, represents absence
  • Can be rebound: ptr = &y; changes what ptr points to
  • Requires null checks: Always check if (ptr != nullptr) before dereferencing
  • Remember: More power means more responsibility

When to Use References

  • Function parameters: Prefer const Type& for read-only, Type& for modification
  • Return values: When returning local/member variables that definitely exist
  • No null needed: When the value must always be present
  • Remember: References are the default choice for function parameters

When to Use Pointers

  • Optional values: When value might be absent (use nullptr)
  • Rebinding needed: When you need to point to different objects over time
  • Dynamic memory: Working with new/delete or smart pointers
  • Remember: Only reach for pointers when you need their unique capabilities

Why This Matters

  • Choosing between pointers and references is one of the most frequent decisions in C++ development.
  • The wrong choice can lead to bugs, memory leaks, or unnecessarily complex code.
  • Understanding this distinction is essential for writing clean, safe, and maintainable C++ applications.

Critical Insight

Follow this simple decision tree: 1. **Can it be null or absent?** → Use pointer 2. **Need to reassign to different objects?** → Use pointer 3. **Working with dynamic memory?** → Use pointer (or smart pointer) 4. **Otherwise** → Use reference (usually const Type&)

References are the default choice for parameters. Only reach for pointers when you specifically need their unique capabilities.

Best Practices

Prefer references for function parameters: Use const Type& for read-only and Type& for modification unless null is possible.

Use pointers for optional values: When a value might be absent, pointers with nullptr are clearer than references with sentinel values.

Always check pointer validity: Before dereferencing, check if (ptr != nullptr) to avoid crashes.

Return references carefully: Never return references to local variables. They dangle when the function returns.

Common Mistakes

Using pointers when references suffice: Passing Player* when const Player& would work makes code harder to read and forces null checks.

Returning references to locals: Player& getPlayer() { Player p; return p; } creates a dangling reference. The local is destroyed!

Not checking null pointers: Dereferencing a null pointer crashes your program. Always validate before use.

Confusing reference assignment with rebinding: ref = obj2 assigns obj2's value to what ref refers to, it doesn't make ref refer to obj2.

Debug Challenge

This function should accept an optional Player that might not exist. Click the highlighted line to fix it:

1 #include <iostream>
2
3 class Player { public: int health; };
4
5 void tryHeal(Player& player) {
6 player.health += 10;
7 }
8
9 int main() {
10 Player* p{nullptr}; // No player yet
11 tryHeal(*p); // Undefined behavior!
12 return 0;
13 }

Quick Quiz

  1. When should you use a pointer instead of a reference?
When the value might be null/optional
Always - pointers are more powerful
Never - references are always better
  1. What's the main advantage of references over pointers?
References can't be null, eliminating a whole class of bugs
References use less memory
References are faster
  1. Which is the best parameter type for a function that reads but doesn't modify a large object?
`const LargeObject&`
`LargeObject`
`LargeObject*`

Practice Playground

Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once