All Courses
Pointers Memory

References in C++


What is a Reference?

A reference is an alias (alternative name) for an existing variable. Once bound, it acts exactly like the original variable.

Basic Concept

int x = 10;
int& ref = x;    // ref is an alias for x

ref = 20;        // Same as x = 20
cout << x;       // 20
cout << ref;     // 20 - they're the same!

Key Properties

  • Must be initialized when declared
  • Cannot be null (unlike pointers)
  • Cannot be rebound to another variable
  • No separate storage (usually) - just an alias

Reference Declaration

Syntax

type& referenceName = existingVariable;

int x = 10;
int& ref = x;      // ref refers to x

double pi = 3.14;
double& piRef = pi;  // piRef refers to pi

string name = "Alice";
string& nameRef = name;  // nameRef refers to name

Initialization Rules

int x = 10;

// Must be initialized at declaration
int& ref = x;     // OK
// int& ref2;     // ERROR: uninitialized reference

// Must refer to a valid object
// int& ref3 = 10;  // ERROR: can't bind to literal

const int& constRef = 10;  // OK: const ref can bind to literal

Reference to Reference

There's no such thing as a reference to a reference. Multiple & symbols are treated as one reference:

int x = 10;
int& ref = x;
int& ref2 = ref;   // ref2 also refers to x (not to ref)

References vs Pointers

Comparison Table

Feature Reference Pointer
Initialization Must initialize Can be null
Reassignment Cannot rebind Can reassign
Null value Not possible Can be nullptr
Syntax Uses . operator Uses -> operator
Memory No separate storage 8 bytes (64-bit)
Address Same as original Own address

Code Comparison

int x = 10;

// Pointer approach
int* ptr = &x;
*ptr = 20;          // Need to dereference
cout << *ptr;       // Need to dereference

// Reference approach
int& ref = x;
ref = 20;           // Direct use
cout << ref;        // Direct use

When to Use Which?

Use References when:

  • You need a guaranteed valid alias
  • The alias won't change
  • Cleaner syntax is preferred
  • Passing to functions (most cases)

Use Pointers when:

  • Null is a valid value
  • Reassignment is needed
  • Dynamic memory allocation
  • Implementing data structures

Reference Use Cases

1. Function Parameters (Pass by Reference)

Avoid copying large objects:

// BAD: Copies the entire vector
void processBad(vector<int> data) { ... }

// GOOD: No copy, uses reference
void processGood(vector<int>& data) { ... }

// BETTER: No copy, can't modify
void processBetter(const vector<int>& data) { ... }

Modify the original:

void increment(int& value) {
    value++;  // Modifies original
}

int main() {
    int x = 10;
    increment(x);
    cout << x;  // 11
}

2. Return by Reference

Return access to internal data:

class Container {
    int data[100];
public:
    int& operator[](int index) {
        return data[index];  // Return reference for assignment
    }
};

Container c;
c[0] = 42;   // Works because operator[] returns reference

3. Range-based For Loops

Modify elements:

vector<int> nums = {1, 2, 3, 4, 5};

// Without reference: modifies copies (useless)
for (int n : nums) {
    n *= 2;  // Does nothing to nums!
}

// With reference: modifies originals
for (int& n : nums) {
    n *= 2;  // Actually doubles each element
}

4. Avoiding Copies

vector<string> names = {"Alice", "Bob", "Charlie"};

// Copies each string (slow for large strings)
for (string name : names) {
    cout << name << endl;
}

// Uses reference (fast, no copy)
for (const string& name : names) {
    cout << name << endl;
}

const References

Purpose

  • Provide read-only access
  • Prevent accidental modification
  • Bind to temporaries and literals

Syntax

const int& constRef = x;   // Can read, can't modify
// constRef = 20;          // ERROR: can't modify through const ref

Binding to Temporaries

const int& ref = 42;       // OK: const ref extends lifetime
const string& str = "Hi";  // OK: binds to temporary string

// int& ref = 42;          // ERROR: non-const ref can't bind to literal

Function Parameters

// Can accept: variables, literals, temporaries
void print(const string& s) {
    cout << s << endl;
}

string name = "Alice";
print(name);        // OK: variable
print("Bob");       // OK: literal
print(name + "!");  // OK: temporary

const Reference vs Value

void func1(string s);           // Makes a copy
void func2(const string& s);    // No copy, read-only

For large objects, prefer const&:

// Efficient for large objects
void process(const vector<int>& data);
void display(const string& text);

// Acceptable for small types
void calculate(int value);
void check(bool flag);

Reference Pitfalls

1. Dangling References

int& dangerous() {
    int x = 10;
    return x;       // DANGER: x is destroyed!
}

int& ref = dangerous();  // Dangling reference!
cout << ref;             // Undefined behavior

Fix: Return by value or ensure the object outlives the reference.

2. Reference to Pointer Confusion

int x = 10;
int* ptr = &x;

int*& ptrRef = ptr;    // Reference to pointer
int& valRef = *ptr;    // Reference to value pointed to

// Be clear about what you're referencing

3. Slicing with References

class Base { virtual void foo() {} };
class Derived : public Base { void foo() override {} };

Derived d;
Base& ref = d;   // OK: reference preserves polymorphism
Base copy = d;   // BAD: slicing - loses Derived part

4. Returning Reference to Local

string& bad() {
    string local = "Hello";
    return local;   // DANGER!
}

// Fix: return by value
string good() {
    string local = "Hello";
    return local;   // OK: copy (or move)
}

Recommended Conventions

✅ Do

// Use const& for input parameters (large objects)
void process(const vector<int>& data);

// Use & to modify original
void increment(int& value);

// Use const& in range-based for when not modifying
for (const auto& item : container) { }

// Use & when modifying in range-based for
for (auto& item : container) { item++; }

// Prefer references over pointers when null isn't needed
void doSomething(int& value);  // Cleaner than int*

❌ Don't

// Don't return reference to local variable
int& bad() {
    int x = 10;
    return x;  // Dangling!
}

// Don't use reference if you need to rebind
int& ref = x;
// ref = y;  // This doesn't rebind, it assigns y's value to x!

// Don't use const& for built-in types (overhead)
void calc(const int& x);  // Just use: void calc(int x);

Guidelines

  1. Function parameters:

  2. Input-only: const T& for large objects, T for small

  3. Output/modify: T&
  4. Optional: use pointer (T*) so null is valid

  5. Return types:

  6. Return by value for local variables

  7. Return by reference for class members

  8. Range-based for:

  9. const auto& when reading
  10. auto& when modifying

Cheat Sheet

// Declaration
int x = 10;
int& ref = x;          // ref is alias for x

// Usage - exactly like the original
ref = 20;              // Same as x = 20
cout << ref;           // Same as cout << x

// const reference
const int& cref = x;   // Read-only alias
const int& lit = 42;   // Can bind to literal

// Function parameters
void modify(int& val);           // Can modify original
void readOnly(const int& val);   // Read-only access

// Range-based for
for (const auto& item : vec) {}  // Read-only
for (auto& item : vec) {}        // Modify

// Cannot do
// int& ref;              // Must initialize
// int& ref = 10;         // Non-const can't bind to literal
// ref = anotherVar;      // Can't rebind (this assigns!)

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples