Single Allocation Optimization (std::make_shared)
Smart Pointers and Memory Management

7.4 Single Allocation Optimization (std::make_shared)

How you initialize your smart pointers has a massive impact on memory fragmentation and allocation speeds. In this lesson, we will examine the physical allocation differences between raw constructor calls and the optimized helper function std::make_shared.

Let's look at the double allocation problem and cache locality.

The Double Heap Allocation Problem

When you initialize a shared pointer using a raw constructor call like std::shared_ptr<int> ptr{new int{42}}, you are triggering two separate heap allocations:

  1. First, the new int{42} call allocates the integer on the heap.
  1. Second, the constructor of std::shared_ptr allocates the Control Block on the heap.

This usually performs two allocator requests, which may come from a fast allocator cache rather than two operating-system calls. It can still add allocation overhead and place related state farther apart in memory.

The Solution: `std::make_shared`

By using std::make_shared<T>(), you tell the compiler to allocate one single contiguous block of heap memory large enough to hold both the managed object T and the Control Block right next to each other.

#include <memory>

int main() {
    // Optimal: Triggers exactly one heap allocation and places control block next to value!
    auto ptr = std::make_shared<int>(42);
    return 0;
}
Single allocation using std::make_shared.

This optimization yields two major benefits:

  • Speed: Usually one allocator request is made instead of two.
  • Cache Locality: Because the object and control block are contiguous, reading one pre fetches the other into the CPU cache, reducing cache line latency.
  • The Box Metaphor: Creating a shared pointer using raw new is like buying a toy at one store, then driving to another store to buy the batteries (two slow trips). Using std::make_shared is like buying a bundle box containing both the toy and the batteries inside (one trip).

The Make Shared Trade Off and Weak Pointer Pinning

There is a critical systems trade off to make_shared. Because the object and control block are fused into the same contiguous block of heap memory, the allocator cannot reclaim that block until both the strong reference count AND the weak reference count hit zero, even if the managed object's destructor has already run.

If your massive 50MB object hits a strong count of zero, its destructor will run, but the fused allocation block cannot be reclaimed by the allocator until both counts hit zero, a single lingering weak_ptr keeps the entire block reserved. If you rely heavily on long-lived weak pointers, consider separate allocations (shared_ptr with new) for very large objects.

Finished reading this lesson?