C++ Modern Pointers & References: Raw Pointers, Smart Pointers, References & Ownership Semantics

C++ Modern Pointers & References: Raw Pointers, Smart Pointers, References & Ownership Semantics
Table of Contents
- The Ownership Decision Tree
- References: Non-Null Aliases
- Lvalue, Rvalue & Forwarding References
- Raw Pointers: The Observer Role
- Pointer Arithmetic: Walking Memory Safely
- unique_ptr in Depth: Factories and Custom Deleters
- shared_ptr in Depth: Control Block and Thread Safety
- std::optional: The Nullable Value Type
- std::observer_ptr: Explicit Non-Ownership (C++26)
- C++ Core Guidelines on Pointer Usage
- Common Pointer Bugs and How to Detect Them
- Frequently Asked Questions
- Key Takeaway
The Ownership Decision Tree
References: Non-Null Aliases
A C++ reference is an alias — another name for an already-existing object. References are:
- Always valid: Cannot be null. Cannot be uninitialized. Cannot "dangle" if used correctly.
- Cannot be reseated: Once bound to an object, always refers to that object.
- Zero overhead: References are typically implemented as pointers internally, but the compiler guarantees the aliasing semantics.
Lvalue, Rvalue & Forwarding References
Understanding value categories is the key to understanding move semantics:
Raw Pointers: The Observer Role
In Modern C++, raw pointers T* have a specific semantic meaning: non-owning observer. If you see T* in Modern C++ code, it means:
- The pointer does NOT own the object (someone else manages its lifetime).
- The pointer might be null (nullable).
- The pointer might be an array (pointer arithmetic context).
Pointer Arithmetic: Walking Memory Safely
Pointer arithmetic is still valid C++ for buffer manipulation — but std::span is the modern wrapper:
unique_ptr in Depth: Factories and Custom Deleters
shared_ptr in Depth: Control Block and Thread Safety
std::optional: The Nullable Value Type
std::optional<T> is a value type that may or may not contain a T. It's the type-safe replacement for nullable pointers to value types:
Common Pointer Bugs and How to Detect Them
| Bug | Description | Detection |
|---|---|---|
| Dangling pointer | Pointer to freed/scoped-out memory | AddressSanitizer (-fsanitize=address) |
| Use-after-free | Accessing deleted object | ASan + smart pointers |
| Double-free | Deleting already-freed memory | ASan + unique_ptr |
| Null dereference | Dereferencing nullptr | Static analysis + if() checks |
| Reference cycle | shared_ptr <-> shared_ptr | Replace with weak_ptr |
| Buffer overflow | Pointer arithmetic past end | ASan + std::span |
| Wild pointer | Uninitialized pointer | Initialize all pointers (= nullptr) |
Frequently Asked Questions
Is it ever OK to use new and delete in Modern C++?
Almost never — only in two scenarios: when implementing a custom allocator or smart pointer itself, or when interfacing with a legacy C API that requires raw allocation. In all other cases, make_unique, make_shared, or stack allocation are correct.
What's the difference between T*, T&, and std::span<T>?
T* is a nullable, possibly-array, non-owning observer. T& is a non-null, non-owning alias to a single object. std::span<T> is a non-owning view over a contiguous sequence with explicit size — the "correct" replacement for (T* ptr, size_t count) pairs.
Does using smart pointers have runtime overhead vs raw pointers?
unique_ptr<T> with default deleter: zero overhead — identical to raw T* in release builds.
shared_ptr<T>: two atomic operations per copy (reference count increment/decrement) — ~10ns on modern hardware. For hot paths, pass const shared_ptr<T>& instead of copying.
Key Takeaway
Modern C++ pointer semantics are about expressing ownership and lifetime in the type system. When you see unique_ptr, you know the object has one owner and will die with it. When you see shared_ptr, multiple owners collaborate. When you see T*, nothing is owned — it's an observer. When you see T&, it's an alias that cannot be null. This semantic clarity is what makes Modern C++ codebases maintainable at scale.
Read next: RAII: Resource Acquisition Is Initialization →
Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.
