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

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

2. Second, the constructor of std::shared_ptr allocates the Control Block on the heap.

This triggers two slow system calls to the operating system memory manager, increases memory fragmentation, and scatters your data across different RAM locations.

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: Only one system call is made to the heap allocator 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

There is a minor trade off: since the object and control block are in the same contiguous block, the physical memory cannot be returned to the operating system until both the strong reference count and the weak reference count hit zero. If you have active weak pointers, the control block must stay alive, meaning the memory for the destructed object cannot be freed until those weak pointers are destroyed.

Finished reading this lesson?