Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 3 of 6

A reference is an alias: a second name for an object that already exists.

#include <iostream>

int main() {
    int x = 42;
    int& r = x;       // r IS x, under another name

    std::cout << x << " " << r << "\n";

    r = 99;           // writes x
    std::cout << x << " " << r << "\n";

    x = 7;
    std::cout << x << " " << r << "\n";
    return 0;
}
42 42
99 99
7 7

There is one int in that program. x and r never disagree, because they are the same object.

You met references already in lesson 5.3, as the way to let a function modify its caller's variable. Now the surrounding machinery makes sense: a reference is, in practice, a pointer the compiler dereferences for you automatically.

The three differences from a pointer

1. A reference must be initialised, and cannot be null.

int x = 1;
int& r = x;      // fine
int& bad;        // error: declared as reference but not initialized

There is no such thing as a reference that refers to nothing. That is the main safety benefit: code taking int& need not check for null, because there is nothing to check.

2. A reference can never be re-seated.

Once bound, a reference refers to that object for its whole life:

#include <iostream>

int main() {
    int a = 1;
    int b = 2;
    int& r = a;

    r = b;        // this does NOT make r refer to b — it assigns b's value to a

    std::cout << a << " " << b << " " << r << "\n";
    return 0;
}
2 2 2

Read that carefully. a became 2. r = b wrote through the alias, exactly as a = b would. Compare with a pointer, where p = &b genuinely re-points.

This is the one place references surprise people. There is no syntax for re-seating a reference, so if you need to change what you are referring to, you need a pointer.

3. A reference needs no dereference operator.

*p versus plain r. Cleaner to read, and one fewer way to make a mistake.

Side by side

#include <iostream>

void by_pointer(int* p) {
    if (!p) return;         // must check
    *p += 1;                // must dereference
}

void by_reference(int& r) {
    r += 1;                 // that's all
}

int main() {
    int x = 10;

    by_pointer(&x);         // caller must take the address
    std::cout << x << "\n";

    by_reference(x);        // caller writes nothing special
    std::cout << x << "\n";
    return 0;
}
11
12

Note the call sites. by_pointer(&x) announces at the call that something may be modified. by_reference(x) looks exactly like a by-value call — you cannot tell from the call alone that x might change. That is the trade: references are cleaner to write and less obvious to read.

const references, and why they are everywhere

A const reference gives access without permission to modify:

#include <iostream>
#include <string>

void print_it(const std::string& s) {
    std::cout << s << " (" << s.size() << " chars)\n";
    // s = "no";   would not compile: s is const
}

int main() {
    std::string name = "Sokha";
    print_it(name);
    print_it("a temporary");     // works: const& binds to temporaries
    return 0;
}
Sokha (5 chars)
a temporary (11 chars)

Two things make const T& the default for parameters that are not small:

  • No copy. Passing a large std::string or std::vector by value copies every byte.
  • It binds to temporaries. print_it("a temporary") creates a temporary std::string, and a const& can bind to it. A plain int& or std::string& cannot bind to a temporary, which is the compiler stopping you from modifying something that is about to disappear.

When to use which

Use a reference by default. Use a pointer when you need one of the things a reference cannot do:

  • It might be absent. A pointer can be nullptr; a reference cannot express "no object".
  • It needs to change target. Walking through an array, re-pointing at a different object.
  • You need arithmetic on it (lesson 7.4).

Modern C++ leans heavily on references, and on smart pointers (Module 9) when ownership is involved. Raw pointers are for non-owning, possibly-absent views.

Try it

Write two functions that both double an int — one taking int*, one taking int&. Then try to write a function that takes int& and makes it refer to a different variable. Explain what happens.

Checkpoint

  • A reference is another name for an existing object, not a copy of it.
  • It must be initialised, can never be null, and can never be re-seated.
  • r = other assigns through the reference; it does not rebind it.
  • No * needed — a reference is used exactly like the object it names.
  • const T& is the default for non-trivial parameters: no copy, and it binds to temporaries.
  • Reach for a pointer only when you need absence, re-targeting, or arithmetic.

Questions about this lesson

Log in to ask a question.