c++vectorc++pmr

std::pmr::vector pass memory resource to custom struct


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;


};

Solution

  • 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.