Skip to content

C++ References

A reference in C++ is an alias – an alternative name – for an existing variable. Once a reference is initialized to refer to a variable, you can use either the variable’s name or the reference’s name to access or modify the variable’s value.

Think of it like giving a nickname to someone. The nickname refers to the same person.

You declare a reference variable by using the ampersand (&) symbol between the type and the reference name. Crucially, a reference must be initialized when it is declared, and it must be initialized with an existing variable (an lvalue).

int originalValue = 10;
// Declare 'ref' as a reference to 'originalValue'
int& ref = originalValue;
// Now 'ref' is an alias for 'originalValue'
std::cout << "Original Value: " << originalValue << std::endl; // Output: 10
std::cout << "Reference Value: " << ref << std::endl; // Output: 10
// Modifying through the reference changes the original variable
ref = 25;
std::cout << "Original Value after modification: " << originalValue << std::endl; // Output: 25
std::cout << "Reference Value after modification: " << ref << std::endl; // Output: 25
// Modifying the original variable also changes what the reference refers to
originalValue = 50;
std::cout << "Reference Value after original modified: " << ref << std::endl; // Output: 50

References and pointers both allow indirect access to variables, but they have key differences:

FeatureReferences (&)Pointers (*)
InitializationMust be initialized at declaration.Can be declared uninitialized or initialized later.
NullabilityCannot be null. Must always refer to a valid object.Can be nullptr (or historically NULL), indicating it doesn’t point to a valid object.
Reassignment (Reseating)Cannot be changed to refer to a different variable after initialization.Can be changed to point to a different variable (or nullptr) at any time.
SyntaxUse the dot (.) operator to access members of the referred object (if it’s a class/struct). Behaves like the original variable.Use the arrow (->) operator to access members via the pointer (or dereference * then use .). Requires dereferencing (*) to get the value.
AddressUsually does not have its own distinct memory address (often optimized away by the compiler).Has its own memory address storing the address of the pointed-to variable.

A primary use of references is for passing arguments to functions by reference. This allows the function to modify the original argument directly, without the overhead of copying.

#include <iostream>
// Function takes an integer by reference
void increment(int& num) { // Note the '&'
num++; // Modifies the original variable passed as argument
}
// Function takes an integer by const reference (avoids copy, prevents modification)
void printValue(const int& val) { // Note 'const' and '&'
std::cout << "Value: " << val << std::endl;
// val++; // Compile error: cannot modify const reference
}
int main() {
int counter = 5;
printValue(counter); // Pass by const reference (no copy)
increment(counter); // Pass by reference
printValue(counter); // See the updated value
return 0;
}

Output:

Value: 5
Value: 6

Using const& is very common for passing large objects (like strings or custom classes) to functions when you only need read access. It avoids the cost of copying the object while guaranteeing the function won’t change it.

Functions can also return references. This is less common but can be useful, for example, in operator overloading (like the assignment operator or [] operator) or when returning an element from a container.

However, never return a reference to a local variable that will cease to exist when the function ends. This results in a dangling reference, which leads to undefined behavior.

#include <vector>
#include <iostream>
// Example: Function returning a reference to an element in a vector
// (Assumes index is valid for simplicity)
int& getElement(std::vector<int>& vec, size_t index) {
return vec[index]; // Returns a reference to the element within the vector
}
/*
// DANGER: DO NOT DO THIS!
int& badReturn() {
int localVar = 10;
return localVar; // Returns reference to localVar, which is destroyed on function exit!
}
*/
int main() {
std::vector<int> numbers = {10, 20, 30};
// Get a reference to the second element (index 1)
int& numRef = getElement(numbers, 1);
std::cout << "Element at index 1: " << numRef << std::endl; // Output: 20
// Modify the element through the returned reference
numRef = 25;
std::cout << "Element at index 1 after modification: " << numbers[1] << std::endl; // Output: 25
// int& dangling = badReturn(); // DANGEROUS!
// std::cout << dangling << std::endl; // Undefined behavior!
return 0;
}

C++11 introduced rvalue references (declared with &&). They primarily bind to temporary objects (rvalues) and are the foundation for move semantics and perfect forwarding, which are advanced techniques for optimizing resource management and writing generic code.