I wrote a custom memory resource to track the number of bytes allocated. I noticed that a std::pmr::vector<std::pmr::vector<int>>
passes its memory resource to the inner vector when a new one is added using emplace()
. I was wondering how it's done and how to write my own struct which is able to receive the resource from the vector and passes it down to its own members.
CustomMemoryResource
class CustomMemoryResource : public std::pmr::memory_resource {
public:
CustomMemoryResource(std::pmr::memory_resource* resource = std::pmr::get_default_resource()) : upstream_{resource} {}
std::size_t size() const noexcept {
return this->size_;
}
private:
std::pmr::memory_resource* upstream_;
std::size_t size_ = 0;
void* do_allocate(std::size_t bytes, std::size_t alignment) override {
this->size_ += bytes;
return this->upstream_->allocate(bytes, alignment);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
this->size_ -= bytes;
this->upstream_->deallocate(p, bytes, alignment);
}
bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
return false;
}
};
TestStruct
struct TestStruct {
TestStruct(std::pmr::memory_resource* resource = std::pmr::get_default_resource()) : vec(resource) {}
std::pmr::vector<int> vec;
};
main.cpp
CustomMemoryResource resource;
std::pmr::vector<std::pmr::vector<int>> vec(&resource);
for (int32_t i = 0; i < 100; i++) {
vec.emplace_back(); // <--- resource is not passed here
for (int32_t j = 0; j < 100; j++) {
vec.back().emplace_back(j);
}
}
std::cout << "Size: " << resource.size() << std::endl;
// Output: Size: 63656
CustomMemoryResource resource2;
std::pmr::vector<TestStruct> vec2(&resource2);
for (int32_t i = 0; i < 100; i++) {
vec2.emplace_back(); // <--- output is as expected if &resource2 is passed here
for (int32_t j = 0; j < 100; j++) {
vec2.back().vec.emplace_back(j);
}
}
std::cout << "Size: " << resource2.size() << std::endl;
// Output: Size: 5656
// Expected: Size:63656
What I have tried so far:
I have added using allocator_type = std::pmr::polymorphic_allocator<>
to the TestStruct
. However, this leads to a compile error:
C2338: static_assert failed: 'If uses_allocator_v<remove_cv_t<T>, Alloc> is true, T must be constructible from either (allocator_arg_t, const Alloc&, Types...) or (Types..., const Alloc&). (N4981 [allocator.uses.construction]/5)'
This forces me to add a rather unintuitive constructor to make it compile, and still the output value is not as expected (Size: 10640
instead of Size: 63656
)
Modified TestStruct
struct TestStruct {
using allocator_type = std::pmr::polymorphic_allocator<>;
template<typename... Types>
TestStruct(std::allocator_arg_t, const allocator_type& alloc, Types...) : TestStruct(alloc.resource()) {}
TestStruct(std::pmr::memory_resource* resource = std::pmr::get_default_resource()) : vec(resource) {}
std::pmr::vector<int> vec;
};
After a lot of trial and error I found a solution which works. Adding a move constructor which accepts an allocator solved the compile error.
struct TestStruct {
using allocator_type = std::pmr::polymorphic_allocator<>; // <--- 1
TestStruct(const allocator_type& alloc) : TestStruct(alloc.resource()) {} // <--- 2
TestStruct(TestStruct&& other, const allocator_type& alloc) : vec{std::move(other.vec)} {} // <--- 3
TestStruct(std::pmr::memory_resource* resource = std::pmr::get_default_resource()) : vec(resource) {}
std::pmr::vector<int> vec;
};
Seems that these three lines are required to get the std::pmr::vector to pass it's memory resource down to the struct automatically.