std::pmr::memory_resource
Abstract base class for C++17 polymorphic memory resources; decouples allocation strategy from container type via runtime polymorphism.
std::pmr::memory_resourcesince C++17An abstract base class in <memory_resource> that defines a uniform virtual interface for memory allocation, allowing the allocation strategy of standard containers to be changed at runtime without altering their type.
Overview
The classical C++ allocator model bakes the allocator into the container's type: a std::vector<int, MyAlloc> and a std::vector<int> are distinct types, making interoperability painful. C++17's Polymorphic Memory Resources (PMR) solve this by separating the type of a container from its allocation behavior through a pointer to a memory_resource.
std::pmr::memory_resource sits in the std::pmr namespace and lives in <memory_resource>. It uses the Non-Virtual Interface (NVI) pattern: three public non-virtual methods (allocate, deallocate, is_equal) delegate to three private pure-virtual methods (do_allocate, do_deallocate, do_is_equal). Derived classes override the do_* methods; callers only ever touch the public interface.
std::pmr::polymorphic_allocator<T> holds a memory_resource* and satisfies the standard Allocator requirements by forwarding to it. Every standard container has a std::pmr:: alias that uses polymorphic_allocator:
namespace std::pmr {
template <typename T>
using vector = std::vector<T, polymorphic_allocator<T>>; // C++17
}Because pmr::vector<int> and pmr::vector<int> with different backing resources share the same type, they can be passed to the same function overloads.
Syntax
#include <memory_resource>
namespace std::pmr {
class memory_resource {
public:
virtual ~memory_resource() = default;
void* allocate(std::size_t bytes,
std::size_t alignment = alignof(std::max_align_t));
void deallocate(void* p,
std::size_t bytes,
std::size_t alignment = alignof(std::max_align_t));
bool is_equal(const memory_resource& other) const noexcept;
protected:
virtual void* do_allocate(std::size_t bytes, std::size_t alignment) = 0;
virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) = 0;
virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
};
// Global resource accessors β all return non-null, non-owning pointers
memory_resource* new_delete_resource() noexcept; // delegates to ::operator new/delete
memory_resource* null_memory_resource() noexcept; // always throws std::bad_alloc
memory_resource* get_default_resource() noexcept; // process-wide default
memory_resource* set_default_resource(memory_resource* r) noexcept; // returns previous
} // namespace std::pmrallocate throws std::bad_alloc (or a derived exception) on failure; it never returns null. is_equal answers the question: "can memory allocated from *this be safely deallocated by other?" This is distinct from object identity.
Built-in Concrete Resources
C++17 ships three concrete memory_resource implementations:
| Type | Thread-safe | Notes |
|---|---|---|
monotonic_buffer_resource | No | Bump-pointer allocator; frees all memory at once on destruction |
unsynchronized_pool_resource | No | Pool allocator for heterogeneous block sizes |
synchronized_pool_resource | Yes | Same as above but mutex-protected |
Both pool resources accept an optional pool_options struct with max_blocks_per_chunk and largest_required_pool_block fields to tune chunk sizes.
Examples
Arena Allocation from the Stack
monotonic_buffer_resource is the workhorse of PMR. It never frees individual allocations β memory is reclaimed in bulk when the resource is destroyed. Combined with a stack buffer, it eliminates heap traffic entirely for short-lived containers.
#include <array>
#include <cstddef>
#include <memory_resource>
#include <string>
#include <vector>
void process_batch(const std::vector<std::string>& input) {
std::array<std::byte, 8192> stack_buf; // C++17: std::byte
// Pass null_memory_resource() as upstream so overflow throws
// rather than silently spilling to the heap.
std::pmr::monotonic_buffer_resource arena{
stack_buf.data(), stack_buf.size(),
std::pmr::null_memory_resource()
};
std::pmr::vector<std::pmr::string> scratch{&arena};
scratch.reserve(input.size());
for (const auto& s : input)
scratch.emplace_back(s, &arena); // string storage also in arena
// All allocations lived in stack_buf β zero heap traffic.
} // arena destructor resets the bump pointer; nothing to free individuallyChaining Resources
Resources can be chained. The upstream resource acts as a fallback or overflow sink:
#include <memory_resource>
#include <vector>
void demo() {
// First tier: a 512-byte monotonic arena backed by the heap
std::pmr::monotonic_buffer_resource fast{512, std::pmr::new_delete_resource()};
// Unsynchronized pool on top: handles varied allocation sizes efficiently
std::pmr::unsynchronized_pool_resource pool{&fast};
std::pmr::vector<int> v{&pool};
for (int i = 0; i < 200; ++i)
v.push_back(i);
}Implementing a Custom memory_resource
The interface is intentionally minimal. A logging or instrumented allocator is straightforward:
#include <cstddef>
#include <memory_resource>
class tracked_resource final : public std::pmr::memory_resource {
public:
explicit tracked_resource(
std::pmr::memory_resource* upstream = std::pmr::get_default_resource())
: upstream_{upstream} {}
std::size_t allocated_bytes() const noexcept { return allocated_bytes_; }
private:
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
void* p = upstream_->allocate(bytes, alignment); // propagates bad_alloc
allocated_bytes_ += bytes;
return p;
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
allocated_bytes_ -= bytes;
upstream_->deallocate(p, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
std::pmr::memory_resource* upstream_;
std::size_t allocated_bytes_ = 0;
};
// Usage
tracked_resource tracker;
std::pmr::vector<double> v{&tracker};
v.resize(1000);
// tracker.allocated_bytes() reflects live heap usage from this containerdo_is_equal returns identity equality because two distinct tracked_resource instances cannot safely exchange memory β each tracks a different counter and may hold a different upstream.
Best Practices
- Always pass the upstream explicitly when overflow behavior matters. A
monotonic_buffer_resourceconstructed without an upstream defaults toget_default_resource(), which silently falls back to the heap. Passingnull_memory_resource()turns overflow into an immediatebad_alloc, surfacing bugs. - Extend lifetime carefully.
polymorphic_allocatorholds a raw, non-owningmemory_resource*. The resource must outlive every container that uses it. Declare the resource before the container in the same scope, or hold it alongside the container in a struct. - Use
unsynchronized_pool_resourcefor heterogeneous allocation sizes in a single thread. Pool resources amortize the overhead of many small allocations into larger slab allocations from the upstream. For single-threaded code,unsynchronized_pool_resourceavoids mutex overhead. - Prefer
pmr::aliases at API boundaries. Acceptingpmr::vector<T>&instead ofvector<T, SomeAlloc>&makes functions allocator-agnostic without templates. - Implement
do_is_equalcorrectly. If your resource uses shared state (a shared pool, a shared arena), two instances referring to the same backing store should returntrue. Getting this wrong causes undefined behavior when the standard library callsdeallocatethrough one allocator for memory obtained through another.
Common Pitfalls
- The default upstream is not
new_delete_resource()formonotonic_buffer_resourceβ it is the result ofget_default_resource()at construction time, which may have been replaced by a priorset_default_resourcecall. Make the upstream explicit. set_default_resourceis not thread-safe. It must be called before any multi-threaded use of the default resource, typically once at program startup.- PMR types and non-PMR types are not the same type.
std::vector<int>andstd::pmr::vector<int>are distinct; you cannot bind a reference of one to the other. This is expected, not a bug, but it surprises engineers migrating code. monotonic_buffer_resourcedoes not call destructors on individual elements. It simply reclaims the raw memory. Elements with non-trivial destructors must still be destroyed explicitly (the container does this on its own destruction), but if you "wink out" the arena without destroying the container first, destructors will not run β a source of resource leaks for types that own handles.- Alignment must be honored in custom implementations.
do_allocatereceives the required alignment; returning misaligned memory is undefined behavior regardless of whether the caller checks it.
See Also
std::pmr::monotonic_buffer_resourceβ bump-pointer resource for arena-style allocationstd::pmr::polymorphic_allocatorβ standard Allocator adapter overmemory_resource*std::pmr::pool_optionsβ tuning struct for pool resourcesstd::allocator_traitsβ the compile-time allocator interface thatpolymorphic_allocatorsatisfies