Skip to content
C++
Library
since C++17
Advanced

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++17

An 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:

cpp
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

cpp
#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::pmr

allocate 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:

TypeThread-safeNotes
monotonic_buffer_resourceNoBump-pointer allocator; frees all memory at once on destruction
unsynchronized_pool_resourceNoPool allocator for heterogeneous block sizes
synchronized_pool_resourceYesSame 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.

cpp
#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 individually

Chaining Resources

Resources can be chained. The upstream resource acts as a fallback or overflow sink:

cpp
#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:

cpp
#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 container

do_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_resource constructed without an upstream defaults to get_default_resource(), which silently falls back to the heap. Passing null_memory_resource() turns overflow into an immediate bad_alloc, surfacing bugs.
  • Extend lifetime carefully. polymorphic_allocator holds a raw, non-owning memory_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_resource for 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_resource avoids mutex overhead.
  • Prefer pmr:: aliases at API boundaries. Accepting pmr::vector<T>& instead of vector<T, SomeAlloc>& makes functions allocator-agnostic without templates.
  • Implement do_is_equal correctly. If your resource uses shared state (a shared pool, a shared arena), two instances referring to the same backing store should return true. Getting this wrong causes undefined behavior when the standard library calls deallocate through one allocator for memory obtained through another.

Common Pitfalls

  • The default upstream is not new_delete_resource() for monotonic_buffer_resource β€” it is the result of get_default_resource() at construction time, which may have been replaced by a prior set_default_resource call. Make the upstream explicit.
  • set_default_resource is 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> and std::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_resource does 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_allocate receives 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 allocation
  • std::pmr::polymorphic_allocator β€” standard Allocator adapter over memory_resource*
  • std::pmr::pool_options β€” tuning struct for pool resources
  • std::allocator_traits β€” the compile-time allocator interface that polymorphic_allocator satisfies