C++Memory

C++ Memory: Stack vs Heap, Smart Pointers, unique_ptr, shared_ptr & Move Semantics

TT
TopicTrick Team
C++ Memory: Stack vs Heap, Smart Pointers, unique_ptr, shared_ptr & Move Semantics

C++ Memory: Stack vs Heap, Smart Pointers, unique_ptr, shared_ptr & Move Semantics


Table of Contents


Stack vs Heap: The Performance Anatomy

mermaid

Stack allocation:

cpp

Heap allocation cost:

  • Mutex acquisition (thread-safe heap)
  • Free block search in allocator's tree/list
  • Page fault on first access (OS maps physical page)
  • Cache miss (newly allocated memory is cold in cache)

Rule: Default to stack. Use heap only when data must outlive its scope, or when the size exceeds the stack limit (~8MB total).


RAII: The Foundation of Modern C++ Memory

RAII (Resource Acquisition Is Initialization) is the core C++ memory safety idiom: resources (memory, file handles, locks, network connections) are acquired in constructors and released in destructors. Since destructors always run when an object leaves scope — even when exceptions are thrown — RAII guarantees no leaks:

cpp

unique_ptr: Exclusive Ownership

std::unique_ptr<T> is a zero-overhead smart pointer providing exclusive ownership. Exactly one unique_ptr owns an object at any time. When the unique_ptr is destroyed, the owned object is automatically deleted:

cpp

Zero overhead: At compile time, unique_ptr with a default deleter is exactly as efficient as a raw pointer. It generates no additional machine code compared to manual new/delete — verified on Compiler Explorer.


shared_ptr: Shared Reference-Counted Ownership

std::shared_ptr<T> allows multiple owners. A reference count tracks how many shared_ptr instances own the object; when the last one is destroyed, the object is deleted:

cpp

shared_ptr overhead: Each make_shared<T> allocates one control block (reference count + deleter + optional allocator). Copying a shared_ptr is atomic increment — thread-safe but not free (~10ns per copy on modern hardware due to the atomic operation). Avoid copying shared_ptr in hot paths; use const shared_ptr& for read-only access.


weak_ptr: Breaking Reference Cycles

shared_ptr reference cycles (A holds B, B holds A) prevent both from ever being deleted — a memory leak. weak_ptr is a non-owning observer of a shared_ptr-managed object:

cpp

Move Semantics: Eliminating Unnecessary Copies

Before C++11, passing or returning a std::vector<int> of 1M elements made a complete deep copy — expensive. Move semantics transfer ownership rather than copying:

cpp

std::move and std::forward

cpp

std::span: Safe Non-Owning Memory Views (C++20)

std::span<T> is a lightweight non-owning view over a contiguous sequence of T — like a pointer + size, but with bounds checking and range-for support:

cpp

Frequently Asked Questions

Should I always prefer unique_ptr over raw pointers? Yes, for owning pointers. Raw pointers are still appropriate for non-owning "observer" pointers (where the ownership is clear and lifetime is managed elsewhere). In C++, if a pointer owns its target, it should be unique_ptr or shared_ptr. If it doesn't own, it's fine to use T* or T& — just document the ownership model.

What is the difference between make_shared and shared_ptr constructor? std::make_shared<T>(args) allocates the control block and the T object in a single allocation — better cache locality, one fewer allocation. std::shared_ptr<T>(new T(args)) does two separate allocations. Always prefer make_shared unless you have a specific reason (e.g., custom deleters or aliasing constructors).

When does std::move NOT actually move? std::move is a cast — it doesn't move anything itself. It enables move semantics by casting to T&&. If the type has no move constructor or move assignment, std::move silently falls back to copy. Also, after return local_var; the compiler applies Named Return Value Optimization (NRVO) or implicit move, so explicitly writing return std::move(local_var) can actually prevent optimization.


Key Takeaway

Modern C++ Memory = RAII + Smart Pointers + Move Semantics. These eliminate manual delete, prevent double-free and use-after-free bugs, and eliminate unnecessary copies of large objects. Once you understand that unique_ptr has zero overhead, shared_ptr has minimal overhead (one atomic increment per copy), and std::move is free (just a cast), you can write memory-safe C++ that matches the performance of bare-metal C.

Read next: Modern C++ Functions: Lambdas, References & Parameter Passing →


Part of the C++ Mastery Course — 30 modules from modern C++ basics to expert systems engineering.