SovranCode
Modern C++ Lesson 27
Course contents50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
Modern C++50 available · 0 planned

Get a program running

01 · C++ Introduction: What Is C++?02 · How to Install a C++ Compiler and Set Up Your Editor03 · C++ Hello World: Your First Program Explained04 · How to Compile and Run C++ Programs05 · C++ Debugging: Warnings and Breakpoints

Work with values and input

06 · C++ Variables: Initialization and Scope07 · C++ Data Types: int, double and Type Conversion08 · C++ const, constexpr and auto Explained09 · C++ Operators: Arithmetic, Comparison and Logic10 · C++ User Input: cin, getline and Validation

Control flow and functions

11 · C++ Conditions: if, else if and else12 · C++ Switch Statement: Cases, break and default13 · C++ For Loop: Syntax and Examples14 · C++ While and Do-While Loops: Examples15 · C++ Functions: Parameters and Return Values

Strings and sequences

16 · C++ Strings: std::string with Examples17 · C++ Vector: std::vector with Examples18 · C++ Arrays: std::array and Bounds Checking19 · C++ Range-Based For Loop and Iterators20 · C++ Vector Practice: Build a Data Summary

Model data with classes

21 · C++ Structs and enum class Explained22 · C++ Classes and Objects: Encapsulation23 · C++ Constructors and Member Initialization24 · C++ Const Member Functions and Composition25 · C++ Copying Objects and the Rule of Zero

Lifetime and resource ownership

26 · C++ Object Lifetime, Destructors and RAII27 · C++ Pointers and References: nullptr and Borrowing28 · C++ unique_ptr: Smart Pointers and Ownership29 · C++ Move Semantics: std::move vs Copying30 · C++ string_view and span: Avoid Dangling Views

Containers and algorithms

31 · C++ map vs unordered_map: Lookup and Examples32 · C++ set, queue and stack: When to Use Each33 · C++ Lambda Expressions and STL Algorithms34 · C++ sort and binary_search: Rules and Complexity35 · C++20 Ranges: Views, filter and transform

Errors, files, and interfaces

36 · C++ optional and variant Explained37 · C++ Exceptions: try, catch and Safe Cleanup38 · C++ File Handling: Read and Write Text Files39 · C++ Input Parsing and Data Validation40 · C++ Polymorphism: Virtual Functions and Interfaces

Generic programming and quality

41 · C++ Templates: Function and Class Examples42 · C++20 Concepts and Template Constraints43 · C++ Unit Testing: Normal and Boundary Cases44 · C++ Sanitizers: Find Memory and Undefined Behavior Bugs45 · C++ Performance: Benchmark Before Optimizing

Build and deliver a complete application

46 · C++ Header Files and Translation Units47 · C++ CMake Tutorial: Build an Executable48 · C++ Dependencies and CTest: Automated Tests49 · C++ Multithreading: Threads and Data Races50 · C++ Command-Line Applications: Help and Release Checks
PREVIOUS LESSONC++ Object Lifetime, Destructors and RAII
NEXT LESSONC++ unique_ptr: Smart Pointers and Ownership
C++ foundations · Lesson 27 About 45 min with practice

C++ Pointers and References: nullptr and Borrowing

Learn C++ pointers and references in simple steps: copies, aliases, addresses, nullptr checks, const access and safe borrowing from a living owner.

Before you begin

Know variables, functions, arrays and object lifetime. You do not need raw new or delete for this lesson; we borrow ordinary objects that already exist.

What you will learn

  • Explain a copy, a reference and a pointer using one integer.
  • Read address-of and dereference symbols in their actual context.
  • Check an optional pointer before accessing its object.
  • Keep borrowed access inside the owner's valid lifetime.
  • Use a checked lookup without confusing access with ownership.

Start with one value, then ask whether you need a copy or access

Imagine that a function needs to work with a score stored somewhere else. It might need its own copy, permission to change the existing score, or a way to report that no score was found. These are different requests. C++ offers values, references and pointers to express them. Learning the difference is more useful than memorizing an asterisk without knowing what problem it solves.

An ordinary int parameter receives a value. Changing that local integer does not change the caller's original integer. An int& parameter is a reference: another way to name an existing integer. Changing it changes that same integer. A pointer, written int*, stores an address that can identify an integer. Unlike a reference, an ordinary pointer can also represent no object with nullptr.

Neither a reference nor a raw pointer automatically owns the object it refers to. In this lesson, owning means being responsible for the object's lifetime or its resource cleanup. Borrowing means using an existing object for a limited time without taking over that responsibility. Think of borrowing a key rather than buying the house, but remember the important limit: C++ does not automatically stop every use of a key after the house is gone.

An owner contains the integer seven. A separate borrowing handle refers to that same living value rather than holding a copy or owning it, while a disconnected nullptr handle represents the absence of a selected object.
A borrow gives access, not ownership. A nullptr check handles absence; it does not prove that a non-null pointer still refers to a living object.
C++ PRACTICE RUNNER

See a copy, a reference and a pointer work on one integer

Read the program from top to bottom. The copy changes on its own; the reference and pointer both access the original integer.

#include <iostream>
int main() {
    int score{7};
    int copy = score;
    int& alias = score;
    int* pointer = &score;
    copy = 20;
    alias = 9;
    *pointer += 1;
    std::cout << "Score: " << score << '\n';
    std::cout << "Copy: " << copy << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Score: 10
Copy: 20

After editing inputs, calculate a new prediction; this reference describes only the starter.

Read & and * according to where they appear

In int& alias = score, the ampersand belongs to the declared type: alias is a reference to an integer. In &score, the ampersand is an expression that obtains score's address. In int* pointer, the asterisk says the variable is a pointer. In *pointer, the asterisk follows that pointer to access the selected integer. The same printed symbol can have different roles, so always read the surrounding declaration or expression.

The first program does not create three versions of score. copy is a separate integer initialized with seven. alias and pointer access the existing score. Setting alias to nine therefore changes score. Adding one through *pointer changes that same score to ten. Meanwhile, copy remains twenty because its assignment changed a different integer. Writing these values beside each line is a good way to check your mental model.

A pointer can later be assigned another valid address or nullptr. An ordinary reference cannot be reseated in the same way: assigning through a reference changes its referred-to object. Keep declarations simple while learning; prefer one pointer declaration per statement instead of relying on a compact multi-variable declaration that makes the type harder to read. Clear names such as selected and original also help show whether the code is selecting an object or creating a new value.

  • score starts at 7; copy receives another 7.
  • copy = 20 changes only the copy.
  • alias = 9 changes score because alias names score.
  • *pointer += 1 changes score again, from 9 to 10.

nullptr means no object: check before following the pointer

A lookup may not find the requested reading. Returning int* lets it return an address on success or nullptr on failure. The caller must test the result before using *selected. Do not treat nullptr as an integer zero stored somewhere: it means there is no selected integer to read. A real integer with value zero can still have a perfectly valid, non-null address.

The second example deliberately selects nothing and prints Not found. Its safe behavior comes from the if check, not from a special protected dereference operation. Accessing through a null pointer is not a reliable way to get an exception or an error message. We do not execute that mistake. The useful repair is to make the absent path explicit before any read or write through the pointer.

A non-null pointer is not automatically a valid pointer. If its object has been destroyed or its vector element was invalidated, the stored address can still be non-null. This is called a dangling pointer: it no longer provides valid access to the old object. A null check answers whether the stored pointer is absent, while lifetime reasoning answers whether an existing borrow may still be used. Both questions matter, and one cannot replace the other.

C++ PRACTICE RUNNER

Handle an absent result without touching an object

The selected pointer is empty, so the program takes the reporting branch instead of reading an integer.

#include <iostream>
int main() {
    const int* selected = nullptr;
    if (selected) std::cout << *selected << '\n';
    else std::cout << "Not found\n";
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Compare with the starter's expected output
Not found

After editing inputs, calculate a new prediction; this reference describes only the starter.

Use const when the borrowed object should only be read

const int* means a pointer through which you cannot change the selected integer. The pointer itself can still select another integer unless it is also declared const. int* const means the pointer cannot be reseated, but the integer can still be changed through it. Start by asking two separate questions: may this handle select something else, and may this access path change the selected value? The placement of const answers them.

The intentional compile-error example tries to change a value through const int*. Repair the example by reading the value instead, or choose a mutable borrowing interface only when the operation really needs permission to write. Do not cast away const to hide a design mismatch. A read-only access path is useful precisely because it lets the compiler reject some accidental writes before the program runs.

A borrowed pointer must not outlive its valid target. Never return the address of an ordinary local integer that is about to leave scope. Do not save a vector element pointer across an operation that may invalidate it. Our lookup returns an address into an array owned by main, and main uses the result before that array leaves scope. The function does not allocate, delete or transfer anything; its job is only selection.

C++ PRACTICE RUNNER

Repair a write through read-only access

Intentional compile error. Replace the write with an observation; the original const access contract should stay intact.

int main() {
    int score{7};
    const int* view = &score;
    *view = 9;
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Build a checked selection before applying a small update

The lab keeps three readings, ten, twenty and thirty, in an array owned by main. Read a one-based position and a change amount on two complete integer lines. The change must be between minus one hundred and one hundred. Malformed or missing required input, or a change outside that range, prints Invalid input and returns status one before the lookup or update happens.

The lookup accepts positions one through three and returns the matching element's address. Other well-formed positions produce nullptr. The caller prints Not found with status zero for an absent result, because a completed search with no match is not a parsing failure. For a match, print Before and After around the update. The bounded initial readings and change keep the arithmetic small, and no operation changes the array's size or lifetime.

Test every valid position, zero, a negative position, position four, zero change and both allowed change endpoints. Also test numeric suffixes and an input line that overflows int. A final valid line without a newline should still work. Explain which object owns the selected integer and why that object is alive at every access. That explanation is the real safety argument; simply saying the pointer was non-null leaves an important part unanswered.

LOCAL TERMINAL COMMANDS

Compile one program and compare its output

The lesson editors run complete programs only when the compiler sandbox is available. You can also save one example as main.cpp and use your installed compiler. Run only the repaired version of an intentional compile-error example.

macOS / Linux (choose your compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson

Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exe
APPLY THE CONCEPT

Independent lab

Select an optional reading from a living array, check the result, and update only the selected value through a borrowed pointer.

Acceptance checklist

  • Own the array {10, 20, 30} in main.
  • Read a whole integer position and a whole integer change in -100..100; reject bad input as Invalid input with status 1.
  • Return an element address only for positions 1..3, validating before converting the index.
  • For no match print Not found with status 0 and do not dereference.
  • For a match print Before and After, using the pointer only while the owning array remains alive.
Need a hint?
  1. Return nullptr before subtracting one for an invalid position.
  2. Keep ownership in main; the lookup function borrows the array.
  3. Check selected before using *selected, then reason separately about the array's lifetime.
C++ PRACTICE RUNNER

Write and run your lab

Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.

#include <iostream>
#include <string>
#include <sstream>
#include <limits>

int main() {
    // Implement the acceptance checklist above.
    return 0;
}

TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Your lab evidence and notes

Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.

Loading local notes…

Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.

Reveal the explained lab solution
C++ PRACTICE RUNNER

Reference lab solution

Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.

#include <iostream>
#include <sstream>
#include <string>
bool readInteger(int& value) {
    std::string line;
    if (!std::getline(std::cin, line)) return false;
    std::istringstream parser{line};
    if (!(parser >> value)) return false;
    parser >> std::ws;
    return parser.eof();
}
#include <array>
#include <cstddef>
int* findReading(std::array<int, 3>& values, int position) {
    if (position < 1 || position > 3) return nullptr;
    return &values[static_cast<std::size_t>(position - 1)];
}
int main() {
    int position{}, change{};
    if (!readInteger(position) || !readInteger(change) || change < -100 || change > 100) {
        std::cout << "Invalid input\n"; return 1;
    }
    std::array<int, 3> readings{10, 20, 30};
    int* selected = findReading(readings, position);
    if (!selected) { std::cout << "Not found\n"; return 0; }
    std::cout << "Before: " << *selected << '\n';
    *selected += change;
    std::cout << "After: " << *selected << '\n';
}
TERMINAL OUTPUT
Predict the output, edit the source, then select Run code.

C++20 compiler runner: source and input are sent only when you press Run to this website's execution endpoint, not an external vendor. Local preview requires Docker and gcc:14; production requires a separately isolated execution service. Network is disabled; execution is limited to 3 seconds, compilation to 10 seconds, memory to 384 MB and output to 32 KB. Single-file programs only. Don't enter secrets. Edits reset on refresh; Copy your work to keep it.

Expected output

Before: 20
After: 25

The array owns the values and outlives the lookup result. The raw pointer only borrows access; it must not delete an array element.

Invalid positions are rejected before subtraction and unsigned conversion. A no-match pointer is checked before any dereference.

The input bounds keep the update between -90 and 130, so the small arithmetic does not rely on unchecked large additions.

Review before moving on

Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.

  • Explain a copy, a reference and a pointer using one integer.
  • Read address-of and dereference symbols in their actual context.
  • Check an optional pointer before accessing its object.
  • Keep borrowed access inside the owner's valid lifetime.
  • Use a checked lookup without confusing access with ownership.

References and further reading

Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.

  • Microsoft: pointers and references
  • C++ Core Guidelines: raw pointers are non-owning by default
KNOWLEDGE CHECK

Check your understanding

Answer all five questions, then review the explanations. Answers are not saved or synced; completion is a separate self-review choice.

01What does a reference do in the first example?
02What does nullptr represent?
03Does a non-null check prove the target is still alive?
04Can you write through const int* in this example?
05Who owns the selected reading in the lab?
PREVIOUS LESSONC++ Object Lifetime, Destructors and RAII
NEXT LESSONC++ unique_ptr: Smart Pointers and Ownership
ON THIS PAGELearning objectivesStart with one value, then ask whether you need a copy or accessRead & and * according to where they appearnullptr means no object: check before following the pointerUse const when the borrowed object should only be readBuild a checked selection before applying a small updateIndependent labReviewKnowledge checkReferences
Course overview