20 Memory management library [mem]

20.4 Memory resources [mem.res]

20.4.5 Pool resource classes [mem.res.pool]

20.4.5.1 Classes synchronized_pool_resource and unsynchronized_pool_resource [mem.res.pool.overview]

The synchronized_pool_resource and unsynchronized_pool_resource classes (collectively called pool resource classes) are general-purpose memory resources having the following qualities:
  • Each resource frees its allocated memory on destruction, even if deallocate has not been called for some of the allocated blocks.
  • A pool resource consists of a collection of pools, serving requests for different block sizes.
    Each individual pool manages a collection of chunks that are in turn divided into blocks of uniform size, returned via calls to do_allocate.
    Each call to do_allocate(size, alignment) is dispatched to the pool serving the smallest blocks accommodating at least size bytes.
  • When a particular pool is exhausted, allocating a block from that pool results in the allocation of an additional chunk of memory from the upstream allocator (supplied at construction), thus replenishing the pool.
    With each successive replenishment, the chunk size obtained increases geometrically.
    [Note 1: 
    By allocating memory in chunks, the pooling strategy increases the chance that consecutive allocations will be close together in memory.
    — end note]
  • Allocation requests that exceed the largest block size of any pool are fulfilled directly from the upstream allocator.
  • A pool_options struct may be passed to the pool resource constructors to tune the largest block size and the maximum chunk size.
A synchronized_pool_resource may be accessed from multiple threads without external synchronization and may have thread-specific pools to reduce synchronization costs.
An unsynchronized_pool_resource class may not be accessed from multiple threads simultaneously and thus avoids the cost of synchronization entirely in single-threaded applications.
namespace std::pmr { struct pool_options { size_t max_blocks_per_chunk = 0; size_t largest_required_pool_block = 0; }; class synchronized_pool_resource : public memory_resource { public: synchronized_pool_resource(const pool_options& opts, memory_resource* upstream); synchronized_pool_resource() : synchronized_pool_resource(pool_options(), get_default_resource()) {} explicit synchronized_pool_resource(memory_resource* upstream) : synchronized_pool_resource(pool_options(), upstream) {} explicit synchronized_pool_resource(const pool_options& opts) : synchronized_pool_resource(opts, get_default_resource()) {} synchronized_pool_resource(const synchronized_pool_resource&) = delete; virtual ~synchronized_pool_resource(); synchronized_pool_resource& operator=(const synchronized_pool_resource&) = delete; void release(); memory_resource* upstream_resource() const; pool_options options() const; protected: void* do_allocate(size_t bytes, size_t alignment) override; void do_deallocate(void* p, size_t bytes, size_t alignment) override; bool do_is_equal(const memory_resource& other) const noexcept override; }; class unsynchronized_pool_resource : public memory_resource { public: unsynchronized_pool_resource(const pool_options& opts, memory_resource* upstream); unsynchronized_pool_resource() : unsynchronized_pool_resource(pool_options(), get_default_resource()) {} explicit unsynchronized_pool_resource(memory_resource* upstream) : unsynchronized_pool_resource(pool_options(), upstream) {} explicit unsynchronized_pool_resource(const pool_options& opts) : unsynchronized_pool_resource(opts, get_default_resource()) {} unsynchronized_pool_resource(const unsynchronized_pool_resource&) = delete; virtual ~unsynchronized_pool_resource(); unsynchronized_pool_resource& operator=(const unsynchronized_pool_resource&) = delete; void release(); memory_resource* upstream_resource() const; pool_options options() const; protected: void* do_allocate(size_t bytes, size_t alignment) override; void do_deallocate(void* p, size_t bytes, size_t alignment) override; bool do_is_equal(const memory_resource& other) const noexcept override; }; }

20.4.5.2 pool_options data members [mem.res.pool.options]

The members of pool_options comprise a set of constructor options for pool resources.
The effect of each option on the pool resource behavior is described below:
size_t max_blocks_per_chunk;
The maximum number of blocks that will be allocated at once from the upstream memory resource ([mem.res.monotonic.buffer]) to replenish a pool.
If the value of max_blocks_per_chunk is zero or is greater than an implementation-defined limit, that limit is used instead.
The implementation may choose to use a smaller value than is specified in this field and may use different values for different pools.
size_t largest_required_pool_block;
The largest allocation size that is required to be fulfilled using the pooling mechanism.
Attempts to allocate a single block larger than this threshold will be allocated directly from the upstream memory resource.
If largest_required_pool_block is zero or is greater than an implementation-defined limit, that limit is used instead.
The implementation may choose a pass-through threshold larger than specified in this field.

20.4.5.3 Constructors and destructors [mem.res.pool.ctor]

synchronized_pool_resource(const pool_options& opts, memory_resource* upstream); unsynchronized_pool_resource(const pool_options& opts, memory_resource* upstream);
Preconditions: upstream is the address of a valid memory resource.
Effects: Constructs a pool resource object that will obtain memory from upstream whenever the pool resource is unable to satisfy a memory request from its own internal data structures.
The resulting object will hold a copy of upstream, but will not own the resource to which upstream points.
[Note 1: 
The intention is that calls to upstream->allocate() will be substantially fewer than calls to this->allocate() in most cases.
— end note]
The behavior of the pooling mechanism is tuned according to the value of the opts argument.
Throws: Nothing unless upstream->allocate() throws.
It is unspecified if, or under what conditions, this constructor calls upstream->allocate().
virtual ~synchronized_pool_resource(); virtual ~unsynchronized_pool_resource();
Effects: Calls release().

20.4.5.4 Members [mem.res.pool.mem]

void release();
Effects: Calls upstream_resource()->deallocate() as necessary to release all allocated memory.
[Note 1: 
The memory is released back to upstream_resource() even if deallocate has not been called for some of the allocated blocks.
— end note]
memory_resource* upstream_resource() const;
Returns: The value of the upstream argument provided to the constructor of this object.
pool_options options() const;
Returns: The options that control the pooling behavior of this resource.
The values in the returned struct may differ from those supplied to the pool resource constructor in that values of zero will be replaced with implementation-defined defaults, and sizes may be rounded to unspecified granularity.
void* do_allocate(size_t bytes, size_t alignment) override;
Effects: If the pool selected for a block of size bytes is unable to satisfy the memory request from its own internal data structures, it will call upstream_resource()->allocate() to obtain more memory.
If bytes is larger than that which the largest pool can handle, then memory will be allocated using upstream_resource()->allocate().
Returns: A pointer to allocated storage ([basic.stc.dynamic.allocation]) with a size of at least bytes.
The size and alignment of the allocated memory shall meet the requirements for a class derived from memory_resource ([mem.res.class]).
Throws: Nothing unless upstream_resource()->allocate() throws.
void do_deallocate(void* p, size_t bytes, size_t alignment) override;
Effects: Returns the memory at p to the pool.
It is unspecified if, or under what circumstances, this operation will result in a call to upstream_resource()->deallocate().
Throws: Nothing.
bool do_is_equal(const memory_resource& other) const noexcept override;
Returns: this == &other.