I'm trying out these new pmr thingies, and stuff seems really interesting.
So in my example I'm hooking up a monotonic_buffer_resource
to a buffer and overlay it with a logging resource to gain insight on what allocations are going on. Then I'm creating a pmr::vector
using that resource and pushing back my pmr aware container type (which is essentially just a wrapper over a pmr::string
.
Now I initialize the temporaries in the vector initializer list with my memory resource and pass them to std::vector. What I thought would happen was that vector would move these objects in place, instead they're copied! I thought that temporaries are always flagged as rvalues when passed into constructors, so I'm a bit surprised. On another note, it also doesn't help when I specifically std::move() these objects. What is going on?
struct pmr_aware_container
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
/* ctors */
// default
pmr_aware_container() : pmr_aware_container{allocator_type{}} {} // delegate to aa constructor
explicit pmr_aware_container(const allocator_type alloc)
: str_("Hello long string!!!", alloc) {
printf("default constructor called!\n");
}
// copy
// pmr_aware_container(const pmr_aware_container&) = default;
pmr_aware_container(const pmr_aware_container& other, allocator_type alloc = {})
: str_(other.str_, alloc) {
printf("Copy constructor called!\n");
}
// move
pmr_aware_container(pmr_aware_container&& other) noexcept
: str_{std::move(other.str_), other.get_allocator() }
{
printf("Noexcept move constructor called!\n");
}
pmr_aware_container(pmr_aware_container&& other, const allocator_type& alloc)
: str_(std::move(other.str_), alloc)
{
printf("Specific move constructor called!\n");
}
// assignement
pmr_aware_container& operator=(const pmr_aware_container& rhs) = default;
pmr_aware_container& operator=(pmr_aware_container&& rhs) = default;
~pmr_aware_container() = default;
allocator_type get_allocator() const {
return str_.get_allocator();
}
std::pmr::string str_ = "Hello long string!!!";
};
class LoggingResource : public std::pmr::memory_resource
{
public:
LoggingResource(std::pmr::memory_resource *underlying_resource) : underlying_resource_{underlying_resource} { }
private:
void *do_allocate(size_t bytes, size_t align) override {
printf("Allocating %d bytes!\n", bytes);
return underlying_resource_->allocate(bytes, align);
}
void do_deallocate(void*p, size_t bytes, size_t align) {
underlying_resource_->deallocate(p, bytes, align);
}
bool do_is_equal(std::pmr::memory_resource const& other) const noexcept override {
return underlying_resource_->is_equal(other);
}
std::pmr::memory_resource* underlying_resource_;
};
int main()
{
std::array<std::byte, 2024> buf;
std::pmr::monotonic_buffer_resource mbs{buf.data(), buf.size()};
LoggingResource log_resource{&mbs};
std::pmr::vector<pmr_aware_container> v{ { pmr_aware_container{ &log_resource}, pmr_aware_container{ &log_resource} }, &log_resource};
}
Output:
Allocating 21 bytes!
default constructor called!
Allocating 21 bytes!
default constructor called!
Allocating 80 bytes!
Allocating 21 bytes!
Copy constructor called!
Allocating 21 bytes!
Copy constructor called!
Resources used in progress:
Creating Allocator-Aware Types by Jason Turner
std::pmr Comes With a Cost by David Sankel
Allocators: The Good Parts by Pablo Halpern
Bonus Question:
I can see from the assembly that the LoggingResource still contains a vtable and hence is not devirtualized. Can I do something to encourage this? I have seen that there was a major improvement in this area from gcc 9 to gcc 11, whereas in gcc 9 even the monotonic_buffer_resource still created a vtable. Anyone knows what I have to do to make the vtable go away for my a custom memory resource?