I've come up with a type that allows me to encapsulate any container class (that supports std::pmr::polymorphic_allocator<T>
) with a buffer and a memory resource object. So it has three member variables. However, one downside of it is that it's by default neither copyable nor movable. So it should better be constructed at the use-site (and not earlier only to be passed around).
I also declared an explicit default constructor to prevent initialization with user-specified arguments since that could lead to errors. The existence of the explicit default constructor causes the trait std::is_aggregate_v
to return false which I think is the correct design choice for this struct.
And I chose buffer_resource_container
as the name of this generic struct template. I could not find a better name.
Then there is also a utility function template reserve_capacity
that takes any std::vector
type and calls its reserve function and does not throw in the case of failure.
Here is a minimal example:
#include <concepts>
#include <memory_resource>
#include <array>
#include <vector>
#include <system_error>
#include <new>
#include <stdexcept>
#include <exception>
#include <iostream>
#include <cstddef>
template <class Container, std::size_t DefaultBufferCapacity>
requires ( std::same_as<typename Container::allocator_type, std::pmr::polymorphic_allocator<typename Container::value_type>> )
struct [[ nodiscard ]] buffer_resource_container
{
alignas ( alignof( typename Container::value_type ) ) std::array<std::byte, DefaultBufferCapacity * sizeof( typename Container::value_type )> buffer;
std::pmr::monotonic_buffer_resource buffer_resource { std::data( buffer ), std::size( buffer ) };
Container container { &buffer_resource };
buffer_resource_container( ) noexcept ( noexcept( Container { &buffer_resource } ) ) = default;
};
template <class ValueType, std::size_t DefaultBufferCapacity>
using buffer_resource_vector = buffer_resource_container<std::pmr::vector<ValueType>, DefaultBufferCapacity>;
template <class ValueType, class Allocator>
[[ nodiscard ]] std::errc inline
reserve_capacity( std::vector<ValueType, Allocator>& vec, const std::size_t new_capacity ) noexcept
{
try
{
vec.reserve( new_capacity );
}
catch ( const std::bad_alloc& )
{
return std::errc::not_enough_memory;
}
catch ( const std::length_error& )
{
return std::errc::value_too_large;
}
catch ( const std::exception& )
{
return std::errc::resource_unavailable_try_again;
}
return std::errc { };
}
int main( )
{
constexpr auto default_capacity { 500uz };
static buffer_resource_vector<char, default_capacity> brv { };
auto& buffer { brv.container };
const auto cap { 200uz };
const auto err_code { reserve_capacity( buffer, cap ) };
if ( err_code != std::errc { } )
{
std::cout << "An error occurred during buffer allocation:"
<< std::make_error_condition( err_code ).message( )
<< "\n";
return 1;
}
}
As can be seen, the requires
clause of the struct enforces a constraint. What other constraints can be employed? Maybe one that makes sure the container has a constructor that takes a const Allocator&
argument (since it is needed in the struct).
The code in main
shows how I think buffer_resource_container
and reserve_capacity
can be used together to improve efficiency by lowering the chances of doing reallocations.
What can be improved here? Are there any issues in the code above? Any suggestions?