-
C has pointers only.
-
C++ has both pointers and references.
-
A reference is an alias for an existing object.
-
References were added in C++ because many uses of pointers are not actually about optional objects or ownership; they're simply about avoiding copies while guaranteeing that an object exists.
int x = 42;
int& ref = x;
ref = 10;
printf("%d\n", x); // 10
-
Once initialized:
-
it must refer to an object
-
it cannot be reseated to refer to another object
-
you use it with normal object syntax (
ref.foo()instead ofptr->foo())
-
-
Cannot be reseated :
int a = 1; int b = 2; int& r = a; r = b; // copies b into a-
Result:
a == 2 b == 2-
And
rstill refers toa.
-
-
-
Can't store "empty" references :
int& r; // error-
You must initialize a reference immediately.
-
Pointers don't have this restriction.
-
-
Const References
const T&:-
See the 'Functions' section.
-
If I want null safety while having indirection, i can't pass a pointer, as the pointer can be null, I have to pass a pointer to an object, and therefore, i have to ensure that i have an object first, to then take its pointer.
so foo(ptr) is not allowed, but foo(&a) would be, considering that a is a valid object (not a pointer)
so cpp enforces this by making foo(a) being the only allowed syntax, and disallowing pointers
Positives
-
Suppose you want a function that modifies an object.
-
With pointers:
void increment(int* x) { (*x)++; } increment(&value); -
With references:
void increment(int& x) { x++; } increment(value); -
The reference version is:
-
cleaner
-
impossible to accidentally pass
nullptr -
clearly expresses "this parameter is required"
-
-
Rule of thumb:
-
Use references when an object must exist.
-
Use pointers when the object is optional, may change, or pointer semantics are required.
-
Negatives
-
References can still dangle :
-
This is probably the biggest misconception.
-
References prevent null references, not dangling references.
int& bad() { int x = 42; return x; // dangling reference }-
This compiles, but using the returned reference is undefined behavior.
-
-
Not explicit at call site :
-
foo(a)doesn't tell me if a can be mutated inside foo. -
In C:
foo(&a);-
Seeing
&aimmediately tells you the function receives an address.
-
-
In C++:
foo(a);-
You need to know whether the signature is
void foo(Object); void foo(const Object&); void foo(Object&); void foo(Object&&); -
Those all have identical call syntax.
-
So C++ deliberately traded explicitness at the call site for cleaner syntax.
-
-
How it works under the hood
-
References are mostly syntax sugar
-
A reference is often implemented internally as a pointer, but the language gives it stronger guarantees.
-
These two are conceptually similar:
void f(int& x);
-
And
void f(int* x);
-
Except the reference version guarantees:
-
xexists -
xisn'tnull
-
In other low-level languages
-
Rust :
-
Rust has explicit references:
fn increment(x: &mut i32)-
Rust references are much stronger than C++ references because the borrow checker enforces that they never dangle and that aliasing rules are respected at compile time.
-
-
Carbon :
-
Intentionally adopts C++-like references
-
-
Vale :
-
Has reference semantics with ownership and regions
-
-
Many language designers consider C++ references to be in an awkward middle ground.
-
They eliminate a few mistakes:
-
no null
-
no reseating
-
-
But they don't solve the hardest problems:
-
dangling references
-
lifetime management
-
aliasing
-
-
So if a language wants memory safety, it often goes much further (like Rust). If it doesn't, it usually sticks with plain pointers because they're simpler and more explicit.
-