C++17stladvanced
std::pmr::unsynchronized_pool_resource
C++17 · Practical Modern · Compiler flag: -std=c++17
Explanation
A pool-based memory resource optimized for single-threaded use. It maintains pools of different block sizes and can reuse freed blocks, providing a good balance between allocation speed and memory reuse without synchronization overhead.
Real-World Scenario
Manage frequent allocations and deallocations of variable-sized objects in a single-threaded event processing loop.
Quick Snippet
snippet.cpp
#include <memory_resource>
#include <list>
std::pmr::unsynchronized_pool_resource pool;
std::pmr::list<int> events(&pool);
events.push_back(1);
events.push_back(2);
events.pop_front(); // block is returned to the pool for reuse
events.push_back(3); // may reuse the freed blockFull Example
unsynchronized_pool_resource.cpp
// CodeNebula - C++17 Example: std::pmr::unsynchronized_pool_resource
// Compile: g++ -std=c++17 -Wall -Wextra -o example unsynchronized_pool_resource.cpp
// Demonstrates std::pmr::unsynchronized_pool_resource, a pool-based memory
// resource for single-threaded use. Unlike monotonic_buffer_resource, it
// recycles deallocated blocks, making it suitable for mixed alloc/dealloc
// patterns where memory reuse matters.
#include <iostream>
#include <memory_resource>
#include <vector>
#include <list>
#include <string>
// Tracking resource to observe upstream allocations
class UpstreamTracker : public std::pmr::memory_resource {
std::size_t total_ = 0;
std::size_t count_ = 0;
std::pmr::memory_resource* upstream_;
public:
explicit UpstreamTracker(std::pmr::memory_resource* up = std::pmr::get_default_resource())
: upstream_(up) {}
std::size_t total() const { return total_; }
std::size_t count() const { return count_; }
protected:
void* do_allocate(std::size_t bytes, std::size_t align) override {
total_ += bytes;
++count_;
return upstream_->allocate(bytes, align);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t align) override {
upstream_->deallocate(p, bytes, align);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
};
int main()
{
// ------------------------------------------------------------------
// 1. Basic usage with a list (frequent small allocations)
// ------------------------------------------------------------------
std::cout << "=== Basic Pool Usage ===" << std::endl;
UpstreamTracker tracker;
std::pmr::unsynchronized_pool_resource pool(&tracker);
std::pmr::list<int> lst(&pool);
for (int i = 1; i <= 10; ++i) lst.push_back(i);
std::cout << " List: ";
for (int x : lst) std::cout << x << " ";
std::cout << std::endl;
std::cout << " Upstream allocs: " << tracker.count() << std::endl;
// ------------------------------------------------------------------
// 2. Memory reuse after deallocation
// ------------------------------------------------------------------
std::cout << "\n=== Memory Reuse ===" << std::endl;
UpstreamTracker tracker2;
std::pmr::unsynchronized_pool_resource reuse_pool(&tracker2);
// Allocate and deallocate in a loop -- pool reuses freed blocks
for (int round = 0; round < 5; ++round) {
std::pmr::vector<int> temp(&reuse_pool);
for (int i = 0; i < 20; ++i) temp.push_back(i);
// temp is destroyed, memory returned to pool (not upstream)
}
std::cout << " After 5 rounds of alloc/dealloc:" << std::endl;
std::cout << " Upstream allocs: " << tracker2.count()
<< " (pool reused blocks)" << std::endl;
// ------------------------------------------------------------------
// 3. Pool options
// ------------------------------------------------------------------
std::cout << "\n=== Custom Pool Options ===" << std::endl;
std::pmr::pool_options opts;
opts.max_blocks_per_chunk = 16;
opts.largest_required_pool_block = 256;
UpstreamTracker tracker3;
std::pmr::unsynchronized_pool_resource custom_pool(opts, &tracker3);
std::pmr::vector<int> v(&custom_pool);
for (int i = 0; i < 50; ++i) v.push_back(i);
std::cout << " max_blocks_per_chunk = 16" << std::endl;
std::cout << " largest_required_pool_block = 256" << std::endl;
std::cout << " Stored " << v.size() << " elements" << std::endl;
std::cout << " Upstream allocs: " << tracker3.count() << std::endl;
// ------------------------------------------------------------------
// 4. release() returns all memory to upstream
// ------------------------------------------------------------------
std::cout << "\n=== release() ===" << std::endl;
{
UpstreamTracker tracker4;
std::pmr::unsynchronized_pool_resource rel_pool(&tracker4);
std::pmr::vector<int> temp(&rel_pool);
for (int i = 0; i < 100; ++i) temp.push_back(i);
temp.clear();
std::cout << " Before release: upstream allocs = " << tracker4.count() << std::endl;
rel_pool.release();
std::cout << " After release: all blocks returned to upstream" << std::endl;
}
return 0;
}
/*
Expected output (counts may vary by implementation):
=== Basic Pool Usage ===
List: 1 2 3 4 5 6 7 8 9 10
Upstream allocs: 2
=== Memory Reuse ===
After 5 rounds of alloc/dealloc:
Upstream allocs: 4 (pool reused blocks)
=== Custom Pool Options ===
max_blocks_per_chunk = 16
largest_required_pool_block = 256
Stored 50 elements
Upstream allocs: 3
=== release() ===
Before release: upstream allocs = 3
After release: all blocks returned to upstream
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example unsynchronized_pool_resource.cpp