I have started using pmr::allocators in my project, and I have seen a lot of performance boost and advantage from using them. I use allocator very similar to what I show int the simple example below:
#include <array>
#include <boost/container/flat_map.hpp>
#include <cassert>
#include <iostream>
#include <memory_resource>
#include <string>
#include <vector>
struct MessageBody {
using map_t = boost::container::flat_map<
char, char, std::less<char>,
std::pmr::polymorphic_allocator<std::pair<char, char>>>;
using vector_t = std::vector<char, std::pmr::polymorphic_allocator<char>>;
MessageBody(std::pmr::memory_resource& mem_v,
std::pmr::memory_resource& mem_m)
: vec_(0, &mem_v), map_(&mem_m) {}
vector_t vec_;
map_t map_;
};
int main() {
std::array<char, 1000> buffer;
buffer.fill('\0');
std::pmr::monotonic_buffer_resource vec_mem(buffer.data(), 500,
std::pmr::null_memory_resource());
std::pmr::monotonic_buffer_resource map_mem(buffer.data() + 500, 500,
std::pmr::null_memory_resource());
{
MessageBody message(vec_mem, map_mem);
message.vec_.push_back('1');
assert(message.vec_.size() == 1);
}
{
MessageBody message(vec_mem, map_mem);
assert(message.vec_.size() == 1); /// I want to adopt the previous class for a new class.
}
}
my question is if there is any way I can adopt the memory_resources
for another class without having the repopulate entire data inside vector
and map
.
the only way I can think of doing that (and I know it is a terrible idea), is to implement a new class that inherits from std::vector, that class has an adopt data method that set the size
inside the vector to the size of the previously used vector without modifying the buffer.
here is a link to godbolt for the example.
What you want is not to adopt the memory resource - because you can and do - but to also store the vector itself there.
You can, e.g. using a helper to create unique_ptr's with custom deleters:
template <typename T>
inline static auto make_T(Mem& mem) {
std::pmr::polymorphic_allocator<T> alloc(&mem);
return std::unique_ptr{
alloc.template new_object<T>(), // allocator is propagated
[alloc](T* p) { alloc.delete_object(p); }};
}
It requires a bit more work when the library support for C++20 is not complete, so let me show that in a live demo on Compiler Explorer
However, there's no way you can "resurrect" a non-trivial type on the same memory using the C++ abstract machine model of memory.
What you really want is to store /live objects/ there and access them from somewhere else.
You can do this using shared-memory allocators, like from Boost Interprocess. You can do it on
Of course there's the benefit of persistence and multi-process access in the latter cases.
If you don't need IPC or persistence, but like the managed segment abilities, use
managed_external_buffer
It takes a few typedefs to set up:
namespace Shared {
using Mem = bip::managed_external_buffer;
template <typename T>
using Alloc = boost::container::scoped_allocator_adaptor<
bip::allocator<T, Mem::segment_manager>>;
template <typename T>
using Vector = boost::container::vector<T, Alloc<T> >;
template <typename K, typename V, typename Cmp = std::less<K>>
using Map =
boost::container::flat_map<K, V, std::less<K>, Alloc<std::pair<K, V>>>;
} // namespace Shared
Note how I used the scoped_allocator_adaptor to get as close to PMR's behaviour of propagating the container allocator to element types according to uses_allocator<>
.
struct MessageBody
{
using map_t = Shared::Map<char, char>;
using vector_t = Shared::Vector<char>;
template <typename Alloc>
MessageBody(Alloc alloc) : vec_(alloc), map_(alloc)
{ }
vector_t vec_;
map_t map_;
};
No more unique_ptr required, your data structure is basically what you had, but we store everything in a single memory resource, so the whole thing can be "resurrected" as one:
int main() {
std::array<char, 1000> buffer;
buffer.fill('\0');
{
Shared::Mem mem(bip::create_only, buffer.data(), buffer.size());
auto& message = *mem.find_or_construct<MessageBody>("message")(
mem.get_segment_manager());
message.vec_.push_back('1');
assert(message.vec_.size() == 1);
}
{
Shared::Mem mem(bip::open_only, buffer.data(), buffer.size());
auto& message = *mem.find_or_construct<MessageBody>("message")(
mem.get_segment_manager());
assert(message.vec_.size() == 1);
}
std::cout << "Bye" << "\n";
}
The get_segment_manager()
call returns a pointer that serves as the initializer for Shared::Alloc<>
instances.
Now it runs passes the assert: Live On Coliru
Just prints
Bye