There is an stateless memory pool allocator class:
template<typename T>
class pool_allocator {
public:
using value_type = T;
using pointer = value_type *;
/* Default constructor */
constexpr pool_allocator( void ) noexcept = default;
/* Converting constructor used for rebinding */
template<typename U>
constexpr pool_allocator( const pool_allocator<U> & ) noexcept {}
[[nodiscard]] pointer allocate( size_t n, [[maybe_unused]] const pointer hint = nullptr ) const noexcept {
return get_pool().allocate( n );
}
void deallocate( pointer ptr, size_t n ) const noexcept {
get_pool().deallocate( ptr, n );
}
private:
/* Must be defined in particular .cpp files */
/* POINT OF INTERREST HERE: */
static auto & get_pool( void ) noexcept;
};
The logic behind is there is the specialization of get_pool()
member function which is intended to return particular memory pool of defined type where the instance of T shall be allocated, for example:
class sample { ... };
in .cpp file:
memory_pool<sample, 10> sample_storage; // memory pool capable of holding up to 10 instances of 'sample'
finally there comes the specialization of get_pool() function template in .cpp file:
template<>
auto & pool_allocator<sample>::get_pool( void ) noexcept {
return sample_storage; // return the memory_pool instance defined above
}
The problem is such template specialization is available only in .cpp compilation unit and prevents the usage of auto get_pool()
in other compilation unit ( the type of auto
placeholder cannot be deduced as the body of get_pool()
function template specialization is not available )
There fore I would like to somehow get rid of
auto
as return type ofget_pool()
.
The problem I face is mainly with the size of the memory_pool
which is unknown to the allocator itself. Anyway, the memory_pool is also my implementation so I can du whatever adoptions required (e.g. further using
declarations or whatever else is needed). Just a skeleton of it:
template<typename T, size_t CAPACITY>
class memory_pool {
public:
using element_type = T;
using pointer = element_type *;
constexpr size_t capacity( void ) noexcept {
return CAPACITY;
}
...
};
Here is the solution I used - to implement the traits class which holds the information about the pool size:
template<typename T>
class memory_pool {
public:
using traits = memory_pool_traits<T>;
using element_type = typename traits::element_type;
using pointer = element_type *;
static constexpr size_t capacity { traits::capacity };
...
};
The allocator:
template<typename T>
class pool_allocator {
public:
using value_type = T;
using pointer = value_type *;
/* Default constructor */
constexpr pool_allocator( void ) noexcept = default;
/* Converting constructor used for rebinding */
template<typename U>
constexpr pool_allocator( const pool_allocator<U> & ) noexcept {}
[[nodiscard]] pointer allocate( size_t n, [[maybe_unused]] const pointer hint = nullptr ) const noexcept {
return get_pool().allocate( n );
}
void deallocate( pointer ptr, size_t n ) const noexcept {
get_pool().deallocate( ptr, n );
}
private:
static memory_pool<T> & get_pool( void ) noexcept;
};
For any particular type there shall be traits class:
// Primary template
template<typename T> struct memory_pool_traits;
Let there be the sample for which I would like to define the memory_pool
:
class sample { ... };
...and aside of that the setup - the traits - for the respective memory_pool
:
template<>
struct memory_pool_traits<sample> {
using element_type = sample;
static constexpr size_t capacity { 10 };
};
In the .cpp
file there is the definition of the pool itself and the get_pool()
function:
memory_pool<sample> sample_storage;
template<>
memory_pool<sample> & pool_allocator<sample>::get_pool( void ) noexcept {
return sample_storage;
}