Home > Software design >  Why does std::pmr::polymorphic_allocator not propagate on container copy construction?
Why does std::pmr::polymorphic_allocator not propagate on container copy construction?

Time:10-05

Why does std::pmr::polymorphic_allocator not propagate on container copy construction?

See the Notes section here

Allocators do propagate on move construction, so this behavior seems to be inconsistent.

Also, with copy elision, this behavior can be somewhat odd. Depending on whether a copy ctor is elided or not, the constructed object can have different allocators: if the copy is elided, then the object will have the allocator of the source object. But if it's not elided, it will have the default allocator.

Consider:

#include <memory_resource>
#include <vector>
#include <cstdio>

char buffer[64];
std::pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));

std::pmr::vector<char> gvec{&pool};

std::pmr::vector<char> gget() {
    return gvec;
}

std::pmr::vector<char> lget() {
    std::pmr::vector<char> lvec{&pool};
    return lvec;
}

int main() {
    printf("default resource:     %p\n", std::pmr::get_default_resource());
    printf("pool:                 %p\n", &pool);
    printf("\n");

    printf("global copy resource: %p\n", gget().get_allocator().resource());
    printf("local copy resource:  %p\n", lget().get_allocator().resource());
}

In this example, gget will return a vector with default resource (even though the copied gvec object uses pool), while lget can return a vector with the pool resource (depending on whether the compiler decides to elide the copy ctor or not). On my machine, this is printed:

default resource:     0x7f67a5be11e8
pool:                 0x55c5924c00e0

global copy resource: 0x7f67a5be11e8
local copy resource:  0x55c5924c00e0

But as far as I understand, it would be perfectly valid if the last line looked like this (i.e., the address of the default resource is printed):

local copy resource:  0x7f67a5be11e8

Is this a design issue, or am I misunderstanding something?

CodePudding user response:

From looking into the various standard proposals, I can find no explanation for making polymorphic allocators not propagate the memory resource on copy construction. The very first proposal (pdf) includes this language, and every version thereafter keeps moving it forward.

However, in searching around for the purpose of the mechanism that prevents this propagation (ie: select_on_container_copy_construction), I found this statement on a now-closed defect:

I think the people using stateful allocators will alter the default behaviour of select_on_container_copy_construction so that it doesn't propagate, but will return a default-constructed one (to ensure a stateful allocator referring to a stack buffer doesn't leak to a region where the stack buffer has gone).

This tracks with something stated in a section of N3525:

Type erasure is a powerful technique, but has its own flaws, such as that the allocators can be propagated outside of the scope in which they are valid

So it seems to me that this is done to make it more difficult for users to accidentally have stack-bound memory_resource types leave the scope in which they are intended to be used.


As for elision, the example you give shows that the circumstance you're concerned about (elision changing the behavior of a copy) pretty much cannot happen:

std::pmr::vector<char> lget() {
    std::pmr::vector<char> lvec{&pool};
    return lvec;
}

This performs a move. That move may be elided, but if it is not, it will be a move.

Indeed, it is essentially impossible to construct a case where elision is an option and vector would be copied. There are ways to turn lvec into a proper lvalue, but all of them shut off elision.

  •  Tags:  
  • c
  • Related