[BUG] Allocator issues in segmented_vector
sh-mochi opened this issue · comments
auto operator=(segmented_vector&& other) noexcept -> segmented_vector&
checks for allocator equality and if allocators are not equal the array is basically rebuilt (with elements moved individually). This is from the current code base:
auto operator=(segmented_vector&& other) noexcept -> segmented_vector& {
clear();
dealloc();
if (other.get_allocator() == get_allocator()) {
m_blocks = std::move(other.m_blocks);
m_size = std::exchange(other.m_size, {});
} else {
// make sure to construct with other's allocator!
m_blocks = std::vector<pointer, vec_alloc>(vec_alloc(other.get_allocator()));
append_everything_from(std::move(other));
}
return *this;
}
Here append_everything_from(std::move(other));
potentially allocates memory and any OOM situation immediately crashes straight into a noexcept boundary. There is also some twisted logic going on. After
// make sure to construct with other's allocator!
m_blocks = std::vector<pointer, vec_alloc>(vec_alloc(other.get_allocator()));
the condition other.get_allocator() == get_allocator()
would always be true, right? Therefore, after
segmented_vector(segmented_vector&& other, Allocator alloc)
: segmented_vector(alloc) {
*this = std::move(other);
}
the contaner will always end up with other
's allocator (regardless of whether alloc==other.get_allocator()
). There is still a silly edge case in the operator=
method I guess: While std::vector<pointer, vec_alloc>(vec_alloc(other.get_allocator()))
actually has an allocator that compares equal to other.get_allocator()
, it is unclear if this allocator will be propagated through the assignment which depends on pocma
, so you can still end up with m_blocks.get_allocator()!=other.m_blocks.get_allocator()
after m_blocks = std::vector<pointer, vec_alloc>(vec_alloc(other.get_allocator()));
. Propose fix:
auto operator=(segmented_vector&& other) noexcept -> segmented_vector& {
static_assert(vec_alloc has pocma==true);
clear();
dealloc();
m_blocks = std::move(other.m_blocks); // without pocma m_block could end up with an incompatible allocator
m_size = std::exchange(other.m_size, {});
return *this;
}
Here are some other issues regarding allocators that should be rather easy to fix:
Allocator is not propagated as it should be.
segmented_vector(segmented_vector const& other) { // missing initializer :m_blocks(other.m_blocks.get_allocator()) {
append_everything_from(other);
}
Doesn't propagate the allocator of other:
auto operator=(segmented_vector const& other) -> segmented_vector& {
if (this == &other) {
return *this;
}
clear();
append_everything_from(other);
return *this;
}
This looks off - isn't this a use before init of get_allocator()
?
segmented_vector(segmented_vector&& other) noexcept
: segmented_vector(std::move(other), get_allocator()) {}
When it comes to the excellent (cough) allocator requirements, maybe an honest
static_assert(pocca && pocma && pocs, "Go away!")
would go a long way.