C++Memory

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

TT
TopicTrick Team
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

mermaid

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.
cpp

Lvalue, Rvalue & Forwarding References

Understanding value categories is the key to understanding move semantics:

cpp

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).
cpp

Pointer Arithmetic: Walking Memory Safely

Pointer arithmetic is still valid C++ for buffer manipulation — but std::span is the modern wrapper:

cpp

unique_ptr in Depth: Factories and Custom Deleters

cpp

shared_ptr in Depth: Control Block and Thread Safety

cpp

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:

cpp

Common Pointer Bugs and How to Detect Them

BugDescriptionDetection
Dangling pointerPointer to freed/scoped-out memoryAddressSanitizer (-fsanitize=address)
Use-after-freeAccessing deleted objectASan + smart pointers
Double-freeDeleting already-freed memoryASan + unique_ptr
Null dereferenceDereferencing nullptrStatic analysis + if() checks
Reference cycleshared_ptr <-> shared_ptrReplace with weak_ptr
Buffer overflowPointer arithmetic past endASan + std::span
Wild pointerUninitialized pointerInitialize all pointers (= nullptr)
bash

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.