Custom Allocators and Polymorphic Memory Resources (std::pmr)
Smart Pointers and Memory Management

7.5 Custom Allocators and Polymorphic Memory Resources (std::pmr)

In latency critical systems (such as high frequency trading algorithms or game loop renderers), making dynamic heap allocations during runtime is strictly forbidden. C++17 solved this by introducing Polymorphic Memory Resources (std::pmr), allowing developers to specify custom allocators at runtime.

Let's look at placement new, arena allocators, and the PMR standard library classes.

Placement New: Constructing in Place

Before looking at PMR, we must understand how to construct an object in pre allocated memory. Standard new allocates memory and calls the constructor. Placement New bypasses allocation entirely, constructing the object at a specific memory address you provide:

#include <iostream>
#include <new> // Required for placement new

struct Player {
    int score;
    Player(int s) : score{s} {
        std::cout << "Player constructed!\n";
    }
};

int main() {
    char buffer[sizeof(Player)]; // Stack buffer (pre allocated memory)

    // Placement new: Construct Player directly inside our stack buffer!
    Player* playerPtr = new (buffer) Player{100};

    std::cout << "Score: " << playerPtr->score << '\n';

    // Destructor must be called manually since there is no heap delete!
    playerPtr->~Player();
    return 0;
}
Constructing objects using placement new.

The PMR Monotonic Buffer Resource (Memory Arena)

A Monotonic Buffer Resource (often called an Arena Allocator) is a custom memory pool resource. It allocates a fixed buffer (on the stack or heap) once at startup. When you add elements to a container, it constructs them inside this buffer, increasing a pointer offset.

OS Heap Locks vs Pointer Bumping

General-purpose allocation can be costly or variable because an allocator may manage free lists, synchronize under contention, request pages from the operating system, and initialize memory. Most calls to new are handled by a user-space allocator rather than a kernel transition, and modern allocators commonly use per-thread caches. Treat an allocation bottleneck as something to profile, not an assumption.

A monotonic arena commonly serves an allocation by aligning an address and advancing an offset. This can be very cheap when the resource has capacity, but it may fall back to an upstream allocator and still needs a lifetime strategy for constructed objects. Benchmark it against the default allocator for the actual allocation pattern.

#include <iostream>
#include <vector>
#include <memory_resource> // Required for std::pmr containers

int main() {
    // Allocate a 500 byte buffer on the stack
    char buffer[500];

    // Create a PMR monotonic buffer resource wrapping our stack buffer
    std::pmr::monotonic_buffer_resource pool{buffer, sizeof(buffer)};

    // Create a vector that allocates its elements directly inside our stack pool!
    std::pmr::vector<int> numbers{&pool};

    // Pushing elements does zero heap allocations! Blazing fast stack shifts.
    for (int i{0}; i < 100; ++i) {
        numbers.push_back(i);
    }
    return 0;
}
Allocating vectors inside a stack arena resource.
  • The Bookcase Metaphor: Storing variables with the default allocator is like renting a new storage locker every time you buy a book, then driving to return the key when done. Using an arena allocator is like buying a large bookcase once at startup. Placing books on the shelf is instant, and when you move houses, you just throw the whole bookcase away at once.
Chapter 7 Knowledge CheckQuestion 1 of 4

Why does creating a shared pointer via std::make_shared<T>() perform better than calling std::shared_ptr<T>(new T())?

Finished reading this lesson?