Skip to content
C++
Language
since C++11
Beginner

Understand and Use References in C++

Learn to create and use C++ references so you can alias variables, avoid costly copies, and let functions modify caller data safely.

By the end of this page, you will be able to declare references, pass variables to functions by reference so those functions can modify them, and use const references to share large data without copying it. You will also recognise the most common reference mistakes and know how to avoid them.

What and Why

Every variable you create lives somewhere in memory. When you write int x = 5;, the program sets aside a small region of memory, labels it x, and stores the value 5 there.

A reference is a second name β€” an alias β€” for that same region of memory. It does not create a copy of the value; it just gives you another way to reach the exact same bytes.

cpp
int x = 5;
int& ref = x;   // ref is another name for x

After that second line, x and ref are the same thing. Changing one changes the other, because they refer to the same memory location.

Why does this matter?

Two situations come up constantly:

  1. Avoiding copies. If you have a large object β€” say, a vector with ten thousand elements β€” passing it to a function normally makes a full copy of all that data. A reference lets the function work with the original object directly, at almost zero cost.

  2. Letting functions modify caller data. Normally, a function cannot change a variable that belongs to the caller. With a reference parameter, it can.

Think of a reference like a nickname. If your name is "Alexander" and your friends call you "Alex", both names refer to the same person. Telling Alex to stand up and telling Alexander to stand up produce the same result.

Step by Step

Declaring a reference

The & symbol after a type name declares a reference type.

cpp
#include <iostream>

int main() {
    int x = 10;
    int& ref = x;          // ref is an alias for x

    ref = 20;              // modifies x through ref

    std::cout << x << "\n";   // prints 20
    std::cout << ref << "\n"; // prints 20
}

Two important rules apply here:

  • A reference must be initialised when it is declared. There is no such thing as an empty reference.
  • A reference cannot be reseated. Once it is bound to a variable, it stays bound to that variable for its entire lifetime.

References as function parameters

The most common use of references is in function parameters.

cpp
#include <iostream>

void addTen(int& n) {   // n is a reference to whatever the caller passes
    n += 10;
}

int main() {
    int score = 42;
    addTen(score);
    std::cout << score << "\n";   // prints 52
}

Without the &, addTen would receive a copy of score, modify that copy, and discard it β€” the original would stay at 42. The & makes the parameter a reference, so the function modifies the caller's variable directly.

Const references

When you want a function to read a value without copying it and without being able to change it, use a const reference.

cpp
#include <iostream>
#include <string>

void greet(const std::string& name) {   // read-only alias, no copy
    std::cout << "Hello, " << name << "!\n";
    // name = "other";   // compile error β€” const prevents modification
}

int main() {
    std::string username = "Ada";
    greet(username);
}

const std::string& is the idiomatic way to accept any string-like object in a function when you only need to read it. This pattern applies to any type that is expensive to copy.

Common Patterns

Pattern 1 β€” Swap two values

A classic demonstration of output parameters via reference:

cpp
#include <iostream>

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 1, y = 2;
    swap(x, y);
    std::cout << x << " " << y << "\n";   // prints 2 1
}

Both a and b are aliases for the caller's variables, so changes inside swap are immediately visible outside.

Pattern 2 β€” Range-based for loop with references

When iterating over a collection, using a reference in the loop avoids copying each element. Adding const prevents accidental modification.

cpp
#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Read each element without copying
    for (const int& n : numbers) {
        std::cout << n << " ";
    }
    std::cout << "\n";

    // Modify each element in place
    for (int& n : numbers) {
        n *= 2;
    }

    for (const int& n : numbers) {
        std::cout << n << " ";   // prints 2 4 6 8 10
    }
    std::cout << "\n";
}

Pattern 3 β€” Returning a reference to a member

A class can expose its internals through a reference-returning function, allowing callers to read or write a member directly while the class retains ownership.

cpp
#include <iostream>

class Counter {
    int value_ = 0;
public:
    int& value() { return value_; }
    const int& value() const { return value_; }
};

int main() {
    Counter c;
    c.value() = 7;                         // write through reference
    std::cout << c.value() << "\n";        // prints 7
}

This pattern is common in container types (think of std::vector::operator[]).

What Can Go Wrong

Mistake 1 β€” Dangling reference

A reference must never outlive the variable it refers to. If the original variable is destroyed while the reference still exists, any use of the reference is undefined behaviour β€” the program may crash, produce garbage, or appear to work correctly until it doesn't.

cpp
int& danglingExample() {
    int local = 42;
    return local;   // WRONG: returning reference to a variable
                    // that is destroyed when the function returns
}

Fix: never return a reference to a local variable. Return references only to objects whose lifetime extends beyond the function call β€” class members, objects passed in by the caller, or objects with static storage.

Mistake 2 β€” Forgetting const and causing unexpected copies

A non-const reference cannot bind to a temporary value or a literal. This surprises beginners.

cpp
void print(int& n) { /* ... */ }

print(5);       // compile error: cannot bind non-const reference to a temporary

Fix: if the function only reads the value, make the parameter const int&. It will then accept literals, temporaries, and named variables alike.

cpp
void print(const int& n) { /* ... */ }

print(5);       // fine

Mistake 3 β€” Assuming = reseats a reference

Because a reference is an alias, assigning to it modifies the original variable, not the binding.

cpp
int a = 1, b = 2;
int& ref = a;
ref = b;           // this sets a = 2, not "ref now refers to b"

// a is now 2, ref still refers to a

If you need something that can be rebound, use a pointer instead of a reference.

Quick Reference

SyntaxMeaning
int& r = x;r is an alias for x
void f(int& n)n is a reference parameter; function can modify caller's variable
void f(const int& n)read-only reference; no copy, no modification
for (int& e : v)iterate and modify elements of v in place
for (const int& e : v)iterate and read elements of v without copying

Key rules to memorise:

  • A reference must be initialised at the point of declaration.
  • A reference cannot be reseated after initialisation.
  • Never return a reference to a local variable.
  • Prefer const T& for function parameters when the function only needs to read the value.

What's Next

Now that you understand references, you are ready to explore the concepts they enable:

  • Pointers β€” a lower-level tool that can be reseated and can be null; understanding references first makes pointers easier to grasp.
  • Move Semantics β€” C++11 introduced rvalue references (T&&), which power efficient resource transfer.
  • const Correctness β€” a deeper look at how const interacts with references, pointers, and member functions throughout a codebase.
  • Function Overloading β€” now that you can write functions with reference parameters, learn how to provide multiple versions of the same function name.