Last updated:2026-10-12
C++17 PMR Memory Resources¶
1.Introduction¶
Before C++17, the standard library provided std::allocator, and in C++17 this facility was strengthened with the introduction of polymorphic_allocator.
Before C++17, if we wanted to use std::allocator to build a custom memory pool, we could not use the traditional approach of virtual polymorphism, because std::allocator does not provide virtual functions. Inheritance is therefore clearly not a good choice, but we can use it as a class member instead, as shown below:
template <typename Allocator = std::allocator<uint8_t>>
class MemoryPool {
public:
explicit STLMemoryPool(const Allocator& alloc) : alloc_(alloc) {}
Status Allocate(int64_t size, uint8_t** out) {
try {
*out = alloc_.allocate(size);
} catch (std::bad_alloc& e) {
return Status::OutOfMemory(e.what());
}
return Status::OK();
}
void Free(uint8_t* buffer, int64_t size) {
alloc_.deallocate(buffer, size);
}
private:
Allocator alloc_;
};
Before C++17, if we wanted to print something, or perform some custom operations, when memory is allocated/freed, there were two ways to do it:
- Custom global new/delete
void* operator new(std::size_t size) {
void* ptr = malloc(size);
std::cout << "Allocated: " << size << " bytes at address " << ptr << std::endl;
return ptr;
}
void operator delete(void* ptr, std::size_t n) noexcept {
std::cout << "Deallocated: " << n << " bytes at address " << ptr << std::endl;
free(ptr);
}
- A custom allocator
class allocator {
public:
using value_type = T;
value_type* allocate(std::size_t n) {
value_type* p = static_cast<value_type*>(::operator new(n * sizeof(value_type)));
std::cout << "Allocated: " << n * sizeof(value_type) << " bytes at address "
<< static_cast<void*>(p) << std::endl;
return p;
}
void deallocate(value_type* p, std::size_t n) noexcept {
std::cout << "Deallocated: " << n * sizeof(value_type) << " bytes at address "
<< static_cast<void*>(p) << std::endl;
::operator delete(p);
}
};
std::vector<int, allocator<int>> vec;
So, besides these, what other options are there?
Since C++17, we can use the polymorphic memory management tools, polymorphic memory resources (pmr). Usage:
#include <memory_resources>
pmr::monotonic_buffer_resource res(10);
pmr::vector<vector<int>> vec1(&res);
For the problem above, we can inherit from memory_resource; for an explanation of the code below, see memory_resource further down.
class TrackAllocator : public std::pmr::memory_resource {
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
void* p = std::pmr::new_delete_resource()->allocate(bytes, alignment);
std::cout << "do_allocate: " << bytes << " bytes at " << p
<< " alignment: " << alignment << std::endl;
return p;
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
std::cout << "do_deallocate: " << bytes << " bytes at "
<< " alignment: " << alignment << std::endl;
return std::pmr::new_delete_resource()->deallocate(p, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return std::pmr::new_delete_resource()->is_equal(other);
}
};
With this, we have achieved the effect shown above.
2.memory_resource¶
The implementation of the std::par::memory_resource class in the STL is fairly simple; here is a pseudo-code implementation:
class memory_resource {
static const size_t __max_align = alignof(max_align_t);
public:
virtual ~memory_resource();
void* allocate(size_t __bytes, size_t __align = __max_align) {
return do_allocate(__bytes, __align);
}
void deallocate(void* __p, size_t __bytes, size_t __align = __max_align) {
do_deallocate(__p, __bytes, __align);
}
bool is_equal(const memory_resource& __other) const noexcept {
return do_is_equal(__other);
}
private:
virtual void* do_allocate(size_t, size_t) = 0;
virtual void do_deallocate(void*, size_t, size_t) = 0;
virtual bool do_is_equal(memory_resource const&) const noexcept = 0;
};
Unlike the default std::allocator, memory_resource provides polymorphic capabilities, allowing users to override the following three interfaces through inheritance:
- do_allocate
- Allocates memory of a given size and with a given alignment requirement.
- do_deallocate
- Frees memory previously allocated via
do_allocate. - do_is_equal
- Checks whether this memory resource object is equal to another memory resource object.
Therefore, our TrackAllocator above simply overrides these three function interfaces.
Careful readers will have noticed that besides these interfaces, memory_resource also exposes three public interfaces. In addition, it provides these global interfaces:
memory_resource* new_delete_resource() noexcept;
memory_resource* null_memory_resource() noexcept;
memory_resource* get_default_resource() noexcept;
memory_resource* set_default_resource(memory_resource* __r) noexcept;
- new_delete_resource
Returns a memory_resource pointer that allocates and deallocates memory using the new and delete operators. This resource is implemented with the global new and delete operators, which makes it the default memory resource manager. This memory_resource subclass is __resource_adaptor_imp, which is responsible for overriding the three interfaces above.
- null_memory_resource
Returns a memory_resource pointer representing a null memory resource that does nothing. You can use this resource when you want to test or stub things out without performing actual memory allocation. The implementation in the STL source defines a
inline memory_resource*
null_memory_resource() noexcept {
class type final : public memory_resource {};
}
type, as a subclass, overrides the three interfaces above; the do_allocate interface throws an allocation-failure exception, because semantically allocation is simply not allowed.
So when we use null_memory_resource, we need a try/catch. For example: a string whose character count is less than 16 is allocated on the stack, in which case our memory_resource here is not used, so things run normally; once the length is greater than or equal to 16, a std::bad_alloc exception is thrown.
std::pmr::memory_resource* default_resource = std::pmr::null_memory_resource();
std::pmr::set_default_resource(default_resource);
try {
std::pmr::vector<int> data{1};
} catch (std::bad_alloc e) {
std::cerr << "bad_alloc is thrown vector" << std::endl;
}
std::pmr::string str1{"hello world hel"};
std::cerr << str1 << " length:" << str1.size() << std::endl;
try {
std::pmr::string str1{"hello world hel0"};
} catch (std::bad_alloc e) {
std::cerr << "bad_alloc is thrown string" << std::endl;
}
- get_default_resource
Returns the current default global memory resource manager. By default it is the same resource returned by new_delete_resource(), but the default resource can be changed via the set_default_resource function.
- set_default_resource
Sets the memory resource manager. The argument may be null (restoring the default resource); otherwise it is set to the passed resource, and the previous default resource is returned.
3.Memory reuse¶
std::pmr::monotonic_buffer_resource is a buffer with significant advantages for memory reuse. monotonic_buffer_resource inherits from memory_resource and overrides the three interfaces above; internally it holds an upstream memory_resource, and when its current memory is insufficient it fetches more from the upstream. This helps reduce memory fragmentation and improve memory utilization.
Below are some ways to use std::pmr::monotonic.
Usage 1 - Create a monotonic_buffer_resource with a fixed buffer:
char buf1[30] = {};
std::fill_n(std::begin(buf1), std::size(buf1) - 1, '_');
std::pmr::monotonic_buffer_resource pool1{std::data(buf1), std::size(buf1)};
std::pmr::vector<int> myVec1{&pool1};
Usage 2 - Create a monotonic_buffer_resource with an upstream resource:
std::pmr::memory_resource* upstreamResource2 = std::pmr::get_default_resource();
std::pmr::monotonic_buffer_resource pool2(upstreamResource2);
{
std::pmr::vector<double> numbers(&pool2);
for (double d = 0.1; d < 1.0; d += 0.1) {
numbers.push_back(d);
}
}
Usage 3 - Inherit from std::pmr::monotonic_buffer_resource:
class MyMonotonicBufferResource : public std::pmr::monotonic_buffer_resource {
public:
using std::pmr::monotonic_buffer_resource::monotonic_buffer_resource; // Inherit constructors
// Public function to access do_allocate
void* allocate(std::size_t bytes, std::size_t alignment) {
return do_allocate(bytes, alignment);
}
// Public function to access do_deallocate
void deallocate(void* p, std::size_t bytes, std::size_t alignment) {
do_deallocate(p, bytes, alignment);
}
};
MyMonotonicBufferResource pool3;
// Allocate memory
void* mem = pool3.allocate(100, alignof(int));
// Use the allocated memory (this is just an example)
std::pmr::vector<int> numbers({1, 2, 3}, &pool3);
// Deallocate memory
pool3.deallocate(mem, 100, alignof(int));
std::cout << "Usage 3 - Inherit: \n\n";
4.pool resource¶
synchronized_pool_resource is a thread-safe memory pool resource class. It is designed to make memory allocation and deallocation safe in multi-threaded environments. When multiple threads concurrently attempt to allocate or free memory, synchronized_pool_resource uses synchronization mechanisms to ensure thread safety. This usually incurs some performance overhead, because locking is required to protect the shared memory pool.
Its counterpart is unsynchronized_pool_resource, where the user is responsible for guaranteeing thread safety.
The source implementation of synchronized_pool_resource is really just mutex + unsynchronized_pool_resource.
class synchronized_pool_resource : public memory_resource {
public:
// some interface
private:
mutex __mut_;
unsynchronized_pool_resource __unsync_;
};
pool_options can be used to configure these memory pools.
- max_blocks_per_chunk
The maximum number of blocks a chunk may contain. It limits how much memory the pool fetches from the upstream memory resource at one time. If set to 0, the kernel modifies pool_options through the munge_options function.
- largest_required_pool_block
The largest block size the memory pool can handle.
Related Topics¶
- C++17 [[nodiscard]] Attribute | C++那些事
- C++17 std::string_view | C++那些事
- C++17 Seventeen Advanced Features | C++那些事
- C++17 Class Template Argument Deduction CTAD | C++那些事
- C++17 optional_ref | C++那些事
- C++17 std::variant | C++那些事
- C++17 Structured Bindings | C++那些事
- C++17 Template auto Deduction | C++那些事
- C++17 Variable Templates | C++那些事