Memory Pools

By the end of this lesson you'll understand why per-object new / delete is slow in a hot loop, and you'll be able to build a fixed-block pool allocator and a bump-pointer arena by hand, recycle objects with an object pool, and reach for std::pmr when you want the speed without writing the allocator yourself.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Calling new for every object is like driving to the store for one ingredient each time you cook — generic, flexible, and slow because each trip has fixed overhead. A pool is a tray of identical lunchboxes by the door: grab one when you need it, drop it back when you're done, and an empty box is instantly reusable — no searching, no oddly-shaped gaps. An arena is a whiteboard: you keep writing left to right (bumping a pointer), and when the meeting ends you wipe the whole board in one stroke instead of erasing each note. General new / delete is the all-purpose store; pools and arenas are purpose-built and far faster for the right pattern .

A pool or arena removes nearly all of this for a known pattern: allocation becomes "pop one node" or "bump a pointer", and the objects sit packed together for cache-friendly iteration. That's why game engines, network servers, and compilers lean on them in their hottest loops.

1. The Fixed-Block Pool Allocator

A pool carves one big block into equal-sized slots and hands them out one at a time. The clever bit is the free list : while a slot is unused, you reuse its own bytes to store a pointer to the next free slot. Allocation pops the head of that list and deallocation pushes a slot back onto it — both are O(1) with zero extra memory. Read this worked example carefully; every non-obvious line is commented with what it does.

A raw byte pool is fine for learning, but in real code you want a typed object pool that constructs a real object in the slot. That needs placement new — new (ptr) T(args) runs a constructor on memory you already own, without allocating anything. Because placement new never pairs with delete , you must call the destructor yourself with ptr->~T() before recycling the slot.

2. The Arena (Bump) Allocator

An arena (also called a bump allocator) is even simpler than a pool. It keeps one cursor — an offset — and every allocation just hands back the current position then moves the cursor forward. You never free individual objects; instead you reset() the whole arena, which rewinds the cursor to 0 and reclaims everything at once in O(1) . This is ideal for data with a clear phase : per-frame game state, per-request server scratch memory, or a compiler's AST nodes. Now you finish one.

The program below is almost complete — fill in the two blanks marked ___ using the // 👉 hints, then run it and check it against the expected output.

3. std::pmr — Standard Allocators Without the Boilerplate

Hand-writing allocators is great for understanding, but C++17 gives you a ready-made framework: std::pmr (polymorphic memory resources). A std::pmr::memory_resource is an object that knows how to hand out memory, and pmr containers like pmr::vector take their memory from it. Two come built in: monotonic_buffer_resource is an arena (bump, reset on destruction), and unsynchronized_pool_resource is a pool. You get custom-allocator speed without writing the allocator — and you can swap strategies without changing the container's type.

Pools and arenas hand out raw bytes , not constructed objects. Placement new bridges that gap: new (ptr) T(args) calls T 's constructor at the address ptr and allocates nothing. The price is symmetry — there is no "placement delete", so you must run the destructor yourself: ptr->~T(); . Forget that, and any work the destructor does (closing files, freeing inner buffers) silently leaks.

Alignment matters too: a double usually must sit on an 8-byte boundary. A union-of- T slot (as in the object pool) is automatically aligned for T ; a raw char buffer is not, so a real arena rounds each offset up to the right boundary before handing it out.

Key calls: new (ptr) T(args) (placement new), ptr->~T() (manual destructor), #include for std::pmr .

No blanks this time — just a brief and an outline. Build a fixed-block pool for a tiny Token type, hand out and recycle slots, and prove a freed slot gets reused by comparing pointers. This is the exact pattern a real engine uses for particles, entities, and packets.

Practice quiz

Why is per-object new/delete slow in a hot loop compared to a pool?

  • It searches free lists, may split/merge blocks, and often takes a lock
  • It always zero-initialises the memory it returns
  • It compresses the heap on every call
  • It runs the constructor twice for safety

Answer: It searches free lists, may split/merge blocks, and often takes a lock. A general allocator does bookkeeping (find/split/merge a block) and is usually thread-safe (a lock), while a pool just pops one node.

In a fixed-block pool's free list, where is the 'next free slot' pointer stored?

  • In a separate side table indexed by slot number
  • Inside the unused slot's own bytes
  • In a global hash map keyed by address
  • At the end of the buffer, after all slots

Answer: Inside the unused slot's own bytes. While a slot is free its bytes are reused to hold the next pointer, so the pool needs zero extra bookkeeping memory.

What are the time complexities of pool allocate and deallocate?

  • O(n) allocate, O(1) deallocate
  • O(log n) for both
  • O(1) for both — pop the head, push it back
  • O(n) for both

Answer: O(1) for both — pop the head, push it back. Allocation pops the head of the free list and deallocation pushes a slot back, both O(1).

What does placement new — new (ptr) T(args) — do?

  • Allocates fresh memory and constructs T in it
  • Runs T's constructor on memory you already own, allocating nothing
  • Frees ptr and then reallocates T
  • Only zeroes the bytes at ptr

Answer: Runs T's constructor on memory you already own, allocating nothing. Placement new constructs an object at an address you provide; it allocates no memory itself.

After constructing an object with placement new, how do you destroy it before recycling the slot?

  • Call delete on the pointer

There is no placement delete, so you must run the destructor yourself with ptr->~T(); never call delete on placement-new memory.

How does an arena (bump) allocator free everything?

  • It calls delete on each object in reverse order
  • It walks a free list and frees each node
  • It resets the offset cursor back to 0 in O(1)
  • It cannot free — the OS reclaims it at exit

Answer: It resets the offset cursor back to 0 in O(1). An arena hands out memory by bumping an offset; reset() rewinds the offset to 0, reclaiming everything at once.

In the arena's allocate(), how is the cursor advanced after handing back the current position?

  • offset = offset + bytes;
  • offset = 0;
  • offset = bytes - offset;
  • offset = sizeof(buffer);

Answer: offset = offset + bytes;. Each allocation returns buffer + offset, then moves offset forward by the requested number of bytes.

Which std::pmr resource behaves like an arena (bump-allocate, reclaim on destruction)?

  • std::pmr::synchronized_pool_resource
  • std::pmr::monotonic_buffer_resource
  • std::pmr::new_delete_resource
  • std::pmr::null_memory_resource

Answer: std::pmr::monotonic_buffer_resource. monotonic_buffer_resource bump-allocates and never reuses freed memory, releasing it all when the resource dies.

Which header must you include to use std::pmr, and from which standard?

  • <memory_resource>, C++17
  • <memory>, C++11
  • <pmr>, C++20
  • <allocator>, C++14

Answer: <memory_resource>, C++17. std::pmr (polymorphic memory resources) lives in <memory_resource> and was added in C++17.

When is it appropriate to reach for a custom pool or arena allocator?

  • Always — it is faster than new/delete in every program
  • Only in a proven hot path after a profiler shows allocation is the bottleneck
  • Whenever a class has a destructor
  • Only when the program is single-threaded

Answer: Only in a proven hot path after a profiler shows allocation is the bottleneck. Default new/delete is fine for most code; measure first and use a custom allocator only where allocation is genuinely the bottleneck.