Docs

README

References in C++

Table of Contents

  1. What is a Reference?
  2. Reference Declaration
  3. References vs Pointers
  4. Reference Use Cases
  5. const References
  6. Reference Pitfalls
  7. Best Practices

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

FeatureReferencePointer
InitializationMust initializeCan be null
ReassignmentCannot rebindCan reassign
Null valueNot possibleCan be nullptr
SyntaxUses . operatorUses -> operator
MemoryNo separate storage8 bytes (64-bit)
AddressSame as originalOwn 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)
}

Best Practices

✅ 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:

    • Input-only: const T& for large objects, T for small
    • Output/modify: T&
    • Optional: use pointer (T*) so null is valid
  2. Return types:

    • Return by value for local variables
    • Return by reference for class members
  3. Range-based for:

    • const auto& when reading
    • auto& when modifying

Quick Reference

// 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
README - C++ Tutorial | DeepML