Skip to content
VLSI Mentor

C++ Part I · Module 8 · Pointers and References

Pointers vs References: Choosing Between Them

Chapter 8.5 ended with a table of eight differences between pointers and references and deliberately refused to say which to use. This chapter makes the choice, and the frozen curriculum is unusually direct about how: on whether absence is meaningful and whether rebinding is required, rather than on which one you find more familiar. That phrasing rules something out as well as in, and the exclusion matters, because familiarity is how the decision is usually made. Engineers who came from C reach for pointers, engineers who learned modern C++ reach for references, and both are answering a question about their own history rather than about the interface they are designing. The two questions that actually decide are narrow and answerable. Can this argument legitimately be absent — is there a real meaning to passing nothing? And does anything need to change which object is referred to during its life? A pointer can express both and a reference can express neither, which sounds like an argument for pointers until you notice that the inability to express absence is precisely what guarantees a reference parameter can never be handed a null. That guarantee is why the right answer is a reference more often than habit suggests.

Intermediate16 min readC++Part IFundamentalsReferences

Part I · Module 8 · Chapter 8.6 · Pointers vs References: Choosing Between Them

1. The Decision Is Not a Preference

Chapter 8.5 §7 collected eight differences. Most of them do not decide anything — sizeof reporting differently, needing * to reach the object, supporting arithmetic. Those are consequences, not criteria.

Two rows decide, and the frozen purpose names both:

QuestionA pointerA reference
Can the argument legitimately be absent?yes — nullptrno
Does anything need to be reseated?yesno

If either answer is yes, you need a pointer. If both are no, you want a reference — and "both are no" describes most function parameters in most code.

That is the whole procedure. §3 and §4 measure the two rows; §5 applies them; §6 is about the reasons people actually use instead.

2. The Two Questions as a Grid

Absence meaningfulAbsence not meaningful
Needs reseatingpointerpointer
No reseatingpointerreference

Three of the four cells are pointers, which makes references look like a special case. They are not, because the bottom-right cell is where the overwhelming majority of real parameters land: a function that is given a thing, uses it, and does not need to be given nothing or be redirected elsewhere.

3. Absence: the One Thing a Reference Cannot Express

Azvya Education Pvt. Ltd.VLSI Mentor
absence.cpp — complete program
#include <iostream>

// a pointer parameter CAN mean "there may be nothing"
static void maybe_report(const int* value)
{
    if (value == nullptr) { std::cout << "  no value supplied\n"; return; }
    std::cout << "  value = " << *value << '\n';
}

// a reference parameter CANNOT -- there is no "absent" reference to pass
static void must_report(const int& value) { std::cout << "  value = " << value << '\n'; }

int main()
{
    int n = 7;
    std::cout << "maybe_report(&n):\n";      maybe_report(&n);
    std::cout << "maybe_report(nullptr):\n"; maybe_report(nullptr);
    std::cout << "must_report(n):\n";        must_report(n);
    return 0;
}

Measured output, zero warnings:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
maybe_report(&n):
  value = 7
maybe_report(nullptr):
  no value supplied
must_report(n):
  value = 7

The finding is what is missing. There are three calls and three outputs, and there is no fourth line because there is no fourth call to write. must_report cannot be handed nothing — Chapter 8.5 §5 established that a reference cannot be null, and the compiler rejects every attempt to construct one.

So the asymmetry is exact:

A pointer parameter says "there may or may not be a value here." Every caller may pass nullptr, and every implementation must therefore decide what that means — which is Chapter 8.3 §7's distinction between a legitimate input and a caller's bug.

A reference parameter says "there is a value here." No caller can pass nothing, so no implementation needs to handle it, and no reader has to wonder.

Choosing a pointer adds a state to the interface. Choosing a reference removes one. That is the trade, and it runs the opposite way from how it is usually described.

4. Reseating: When the Target Must Change

The second question. Chapter 8.5 §4 measured it exhaustively: r = b; assigns through the reference, &r never moves, and changing b afterwards leaves r alone. There is no syntax for rebinding.

So if something must refer to one object and then another, a reference cannot do it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int* current = &first;
    // ... later ...
    current = &second;        // fine for a pointer
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int& current = first;
    // ... later ...
    current = second;         // assigns to `first` -- NOT what was meant

This is the question that decides most non-parameter cases. A member that tracks "the thing currently selected", a loop variable walking a chain, a cached result that moves — all need reseating, and all need a pointer.

For parameters it decides far less often, because a parameter's job is usually to be used once and discarded. A parameter that gets reseated inside the function is reseating a local copy, which the caller never sees — so the question for a parameter is really "does the caller need me to redirect something they own", which is rare and is usually spelled as a pointer-to-pointer or a return value instead.

One narrow case is worth naming because it comes up in containers: sometimes you want something with a reference's semantics that can nonetheless be reseated and stored. The standard library provides a wrapper type for exactly that, and it exists precisely because a plain reference cannot. It is not taught in this curriculum, and knowing why it has to exist is the useful part.

5. Applied to Real Signatures

The two questions, run over the kinds of parameter this curriculum has written.

SignatureAbsence?Reseat?Right form
read a large objectnonoconst SampleBlock& — Chapter 6.5
modify the caller's objectnonoSampleBlock& — Chapter 6.4
a fundamental type, readnonoby value — Chapter 6.5 §7
an optional configuration overrideyesnoconst Config* — or std::optional
a result that may not existyesnopointer — or std::optional, Chapter 21.5
the end of a chainyesyespointer
an object this function takes ownership ofyesyespointer — but see below

Two rows deserve a note.

"Optional" is often better served by a type that says so. Chapter 8.3 §8 made this point and it belongs here too: a nullable pointer used purely to mean "may be absent" is the older idiom, and std::optional (Chapter 21.5) makes the absence part of the type rather than a convention a reader has to infer. Use a pointer for optionality when you need a pointer anyway; use std::optional when you do not.

Ownership is a third question this chapter does not answer. A raw pointer says nothing about who is responsible for destroying what it points at, and Chapter 10.9 is the chapter titled with exactly that problem. The modern answer is a smart pointer — Module 14 — which encodes ownership in the type. For now: if the answer to "who deletes this?" is part of what you are trying to express, neither a raw pointer nor a reference expresses it, and the right move is to read Module 14 before inventing a convention.

6. What Does Not Decide It

The frozen purpose rules out one basis explicitly. Here are four that get used, and why each is wrong.

"Which one I am used to." The purpose names this. A C background makes pointers feel natural; a modern C++ background makes references feel natural. Neither is a fact about the interface. The test is §2's grid, and it gives the same answer regardless of who is asking.

Performance. Both are cheap and the difference is not something you should be choosing on. Chapter 6.5 §3 measured the thing that actually matters — avoiding a copy — and both forms avoid it equally. Chapter 8.5 §6's sizeof difference is about the type system, not about cost; the standard does not even say whether a reference occupies storage.

"References are safer." Partly true and routinely overstated. A reference cannot be null and cannot be uninitialized, which removes Chapter 8.3's entire failure mode. It can dangle, exactly as a pointer can — Chapter 8.5 §5 flagged this and Chapter 8.8 is the chapter. So the honest claim is that a reference removes one bug class and not the other, and a codebase that switched to references has eliminated null dereferences and not lifetime bugs.

"A pointer is more explicit at the call site." This one is a real argument and it is worth stating properly rather than dismissing. Chapter 6.4 §4 observed that configure(2, result) gives a reader no indication that result may be modified, because a reference parameter is invisible at the call site. The pointer form makes it visible:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    configure(2, result);         // reference: might this change result?
    configure(2, &result);        // pointer: the & is a signal

That is a genuine trade and different codebases resolve it differently — some style guides require a pointer for out-parameters precisely for this reason. What it is not is a reason to use a pointer for a read-only parameter, which is where the habit usually leaks. Chapter 6.9 §4's answer stands: if the call site cannot show it, the function's name must.

7. The Procedure, Collected

For any parameter or member, in order:

  1. Can it legitimately be absent? If yes → pointer (or std::optional if you need nothing else from a pointer).
  2. Must it be reseated during its life? If yes → pointer.
  3. Is it a fundamental type you only read? If yes → by value (Chapter 6.5 §7).
  4. Will you modify the caller's object? If yes → T& (Chapter 6.4), and name the function after the effect.
  5. Otherwise → const T& (Chapter 6.5).
  6. Separately: does ownership need expressing? If yes → Module 14, not a raw pointer.

Steps 3 to 5 are Chapter 6.5 §6's rule unchanged. What this chapter adds is steps 1, 2 and 6 — the questions that decide whether you are in that rule's territory at all.

8. What to Actually Do

Ask the two questions. Absence, then reseating. Nothing else decides.

Default to a reference when both answers are no, which is most of the time.

Use a pointer when absence is meaningful, and handle it as part of the contract — Chapter 8.3 §7.

Use a pointer when reseating is required, which is common for members and rare for parameters.

Do not choose on familiarity, performance, or a blanket claim about safety.

Do not null-check a reference, and do not add a pointer parameter just so you can.

Name the function after its effect when a reference parameter is modified, since the call site cannot show it — Chapter 6.4 §4.

Reach for std::optional for optionality you do not otherwise need a pointer for, and Module 14 for anything about ownership.

9. Mistakes Worth Making Once

Choosing on familiarity. The frozen purpose names this as the wrong basis, and it is the most common one.

Using a pointer for a required parameter. It adds a state the implementation must handle and the signature does not explain.

Trying to express absence with a reference. Measured: there is no call to write; the compiler rejects every attempt.

Trying to reseat a reference. Measured in Chapter 8.5 §4: it assigns to the referent instead.

Claiming references are safe. They remove null, not dangling — Chapter 8.8.

Choosing on performance. Both avoid the copy; the standard does not even say whether a reference occupies storage.

Using a raw pointer to express ownership. It expresses nothing about who destroys it — Chapter 10.9 and Module 14.

Adding a null check to a reference parameter. It tells every reader the argument might be absent when the type says it cannot be.

10. Interview Relevance

This is a Critical chapter and the question is a design question, which means the reasoning is the answer.

"When would you use a pointer rather than a reference?" The strong answer is the two questions: when absence is meaningful, or when the thing must be reseated. A weak answer lists syntactic differences. If you add that choosing a pointer adds a state the implementation has to handle, you have given the reason rather than the rule.

"Which is safer?" Partly a reference — it removes null and uninitialized — and not entirely, because both can dangle. Candidates who give the unqualified answer usually have not met Chapter 8.8's bug.

"Why not always use pointers, since they can do more?" Because doing more means representing more states, and every representable state is one the implementation must handle and every reader must consider. This is the question that most reliably separates people who have designed an interface from people who have only used one.

11. What You Should Be Able to Say Now

  1. Two questions decide it: can the argument legitimately be absent, and does anything need reseating?
  2. If either is yes → pointer. If both are no → reference.
  3. Most of Chapter 8.5 §7's eight differences are consequences, not criteria.
  4. Measured: a pointer parameter handled nullptr and reported "no value supplied"; there is no call to write for the reference version.
  5. A pointer parameter says "there may or may not be a value"; a reference says "there is one."
  6. Choosing a pointer adds a state to the interface; choosing a reference removes one.
  7. Reseating is impossible for a reference — Chapter 8.5 §4 measured all three ways.
  8. Reseating decides most member cases and few parameter cases.
  9. The full procedure is Chapter 6.5 §6's rule with absence, reseating and ownership added in front of it.
  10. std::optional is better than a nullable pointer when you need nothing else from a pointer — Chapter 21.5.
  11. Ownership is a third question neither construct answers — Chapter 10.9, Module 14.
  12. Familiarity, performance and "safety" do not decide it. The first is named by the curriculum as the wrong basis.
  13. "References are safer" is half true: they remove null and uninitialized, not dangling.
  14. The one real argument for pointers in out-parameters is call-site visibility — Chapter 6.4 §4's &result — and it does not extend to read-only parameters.

12. Exercises

Exercise 1 — run the procedure. For each, give the parameter form and the question that decided it: (a) the divisor in a division function (b) an optional logging prefix (c) a 16 KB block to be zeroed (d) a 16 KB block to be summed (e) the next node in a chain.

Exercise 2 — what is missing. §3's program has three calls and three output lines. Say what a fourth call would have to be, and why it cannot be written.

Exercise 3 — count the states. For void configure(const Config* c) and void configure(const Config& c), list the states each parameter can be in and say what the implementation must do about each.

Exercise 4 — reseat it. Explain why int& current = first; current = second; does not do what its author intended, name the chapter that measured this, and give the version that works.

Exercise 5 — correct the claim. A colleague says "we use references everywhere now, so we do not have pointer bugs". Say precisely which bug class that removes, which remains, and the chapter that covers the remainder.

Exercise 6 — the visibility argument. configure(2, result) does not show that result may be modified. Give the pointer version, say what it buys, and say why that is not an argument for using pointers for read-only parameters.

Exercise 7 — the third question. Widget* make_widget(); returns a pointer. Say what the signature does not tell the caller, what could go wrong as a result, and which module answers it.

13. Summary

  • Two questions: is absence meaningful, and is reseating required? Either yes → pointer; both no → reference.
  • Most of Chapter 8.5's differences are consequences, not criteria.
  • Measured: the pointer version reported "no value supplied"; the reference version has no such call to write.
  • A pointer adds a state; a reference removes one — and every state must be handled and understood.
  • Reseating decides most member cases, few parameter cases.
  • The procedure is Chapter 6.5 §6's rule with absence, reseating and ownership in front.
  • std::optional for pure optionality; Module 14 for ownership.
  • Familiarity, performance and blanket safety claims do not decide it.
  • "Safer" is half true — null and uninitialized go, dangling stays.
  • Call-site visibility is the one real pointer argument, and only for out-parameters.

Every signature in this chapter's §5 table that reads a value without modifying it was written const T&, and that word has been doing load-bearing work since Chapter 6.5 without its own chapter. Once pointers exist it stops being one keyword in one position. A pointer has two things that could be made constant — the object it points at, and which object it points at — so there are three spellings rather than one, two of them differ by where the word sits rather than by any visible logic, and reading them wrongly is how a function ends up promising something it does not deliver. The next chapter is that, and the measurement is three declarations each forbidding a different assignment, with the compiler naming exactly which.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.