Docs
README
References in C++
Table of Contents
- •What is a Reference?
- •Reference Declaration
- •References vs Pointers
- •Reference Use Cases
- •const References
- •Reference Pitfalls
- •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
| 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)
}
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
- •
Function parameters:
- •Input-only:
const T&for large objects,Tfor small - •Output/modify:
T& - •Optional: use pointer (
T*) so null is valid
- •Input-only:
- •
Return types:
- •Return by value for local variables
- •Return by reference for class members
- •
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