Skillversitas

References in C++

A reference is an alias (another name) for an existing variable. References are declared with the & operator after the data type. Once initialized, a reference cannot be changed to point to another variable. References are safer and more intuitive than pointers because they do not require dereferencing and cannot be null.

References are needed for: pass by reference to functions without copying data, returning references from functions, and simplifying data access syntax. Many C++ programmers prefer references over pointers because of the cleaner syntax.

Key differences between references and pointers:
- A reference must be initialized at declaration; a pointer does not have to be.
- A reference cannot be null; a pointer can.
- A reference cannot be reassigned to another variable; a pointer can.
- A reference is dereferenced automatically; a pointer needs the * operator.

Const References

A const reference allows passing data without the ability to modify it. This is very common for function parameters that only read the data, because it avoids the cost of copying while also protecting the data's integrity.

Code Example

#include <iostream>
#include <string>
using namespace std;

struct Student {
    string name;
    int score;
};

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

void printStudent(const Student& m) {
    cout << "Name: " << m.name << ", Score: " << m.score << endl;
}

void addPoints(Student& m, int points) {
    m.score += points;
}

string& largestChar(string& text) {
    char& largest = text[0];
    for (size_t i = 1; i < text.length(); i++) {
        if (text[i] > largest) {
            largest = text[i];
        }
    }
    return largest;
}

int main() {
    // Declaring a reference
    int original = 10;
    int& ref = original; // ref is an alias for original

    cout << "original = " << original << endl;
    cout << "ref = " << ref << endl;

    ref = 30; // changing ref is the same as changing original
    cout << "\nAfter ref = 30:" << endl;
    cout << "original = " << original << endl;
    cout << "ref = " << ref << endl;

    // Reference to a string
    string name = "John";
    string& nameRef = name;
    nameRef += " Smith";
    cout << "\nName: " << name << endl;

    // Pass by reference
    int x = 5, y = 15;
    cout << "\nBefore swap: x=" << x << ", y=" << y << endl;
    swapValues(x, y);
    cout << "After swap: x=" << x << ", y=" << y << endl;

    // Const reference
    const string message = "Hello World";
    const string& messageRef = message;
    cout << "\nMessage: " << messageRef << endl;
    // messageRef += "!"; // ERROR: cannot modify a const reference

    // Const reference to non-const (read-only view)
    int number = 42;
    const int& readonly = number;
    cout << "Readonly: " << readonly << endl;
    // readonly = 100; // ERROR: cannot modify
    number = 100; // POSSIBLE: the original can still be changed
    cout << "After the original is changed: " << readonly << endl;

    // Reference with a struct
    Student student = {"Mike", 85};
    cout << "\n";
    printStudent(student);
    addPoints(student, 10);
    cout << "After adding points: ";
    printStudent(student);

    // Reference to an array element
    int arr[] = {10, 20, 30};
    int& element = arr[1];
    cout << "\nArray[1] via reference: " << element << endl;
    element = 25;
    cout << "After modification: " << arr[1] << endl;

    // Return by reference
    string text = "abcdef";
    cout << "\nLargest character in '" << text << "': ";
    largestChar(text);
    cout << text[0] << " (index 0)" << endl;

    // Reference to a pointer
    int val = 100;
    int* ptr = &val;
    int*& refPtr = ptr; // refPtr is an alias for ptr
    cout << "\nThrough ref to pointer: " << *refPtr << endl;
    int val2 = 200;
    refPtr = &val2; // changes ptr
    cout << "After ptr is changed: " << *ptr << endl;

    // Looping with a const reference
    int numbers[] = {1, 2, 3, 4, 5};
    int total = 0;
    for (const int& a : numbers) {
        total += a;
    }
    cout << "\nTotal: " << total << endl;

    // Comparing references vs pointers
    cout << "\n--- Reference vs Pointer Comparison ---" << endl;
    int value = 50;
    int& refA = value;
    int* ptrA = &value;

    cout << "Reference dereferences automatically: " << refA << endl;
    cout << "Pointer needs dereference: " << *ptrA << endl;
    cout << "Sizeof reference: " << sizeof(refA) << endl; // same as sizeof(int)
    cout << "Sizeof pointer: " << sizeof(ptrA) << endl;   // 8 bytes (64-bit)

    return 0;
}

Code Explanation

  • int& ref = original; — Declares a reference. ref is another name for original. Anything done to ref affects original and vice versa.
  • ref = 30; — Changing ref is the same as changing original. Output: original = 30.
  • string& nameRef = name; — References can refer to any data type, including classes such as std::string.
  • void swapValues(int& a, int& b) — Parameters by reference. The function receives the addresses of the original variables without copying. Operations on a and b modify the original variables.
  • const string& messageRef = message; — A const reference. You cannot modify the value through this reference. Useful for reading data without the risk of modification.
  • const int& readonly = number; — A const reference to a non-const variable. Creates a read-only view: you cannot modify through readonly, but the original variable number can still be changed from elsewhere.
  • printStudent(const Student& m) — Passing a struct by const reference. It avoids copying the entire struct while also guaranteeing that the function does not modify the data. This is best practice for large object parameters.
  • addPoints(Student& m, int points) — Passing by non-const reference because the function needs to modify m.score.
  • int& element = arr[1] — A reference can point to a specific array element. Useful for directly modifying a particular element.
  • largestChar(string& text) — Return by reference. Returns a reference to the largest character in the string. Because it returns a reference, the caller can directly modify the result.
  • int*& refPtr = ptr — A reference to a pointer. refPtr is an alias for ptr. Changing refPtr means changing the pointer ptr itself.
  • for (const int& a : numbers) — A range-based for with a const reference. Efficient for large data types because it does not copy, and safe because it is const.
  • sizeof(refA) vs sizeof(ptrA) — A reference has no memory overhead of its own. The sizeof of a reference is the same as the sizeof of the referenced type. A pointer is always 4 or 8 bytes depending on the architecture.

Everyday Use Cases

Swap function: In a sorting algorithm such as bubble sort, you need to swap two array elements. With references, a swapValues(a, b) function directly modifies the original variables without complicated pointers.

Reading large objects: When calling a function printStudent(const Student& m), the Student data is not copied. For a large struct with many fields, this saves significant time and memory.

Other examples:
- Iterators: for (auto& item : collection) processes collection elements directly without copying.
- Builder pattern: A method returns *this as a reference for method chaining: builder.setName("X").setAge(25).
- Callbacks: References make it easy to pass data to callback functions without copy overhead.

Conclusion

References are a C++ feature that simplifies the use of pointers. References must be initialized, cannot be null, and cannot be reassigned. A const reference is best practice for function parameters that read large data without copying while also protecting it. Use references as the default; use pointers only when you need nullable or reassignable behavior.