Why does std::pmr::polymorphic_allocator
not propagate on container
copy construction?
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.