I've written countless software modules around the common theme of a long lived vector that some times (at unspecified frequency) must have its content updated.
Idiomatic implementation:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
populate(m_vector, whatever_input);
}
Notice that the idiomatic implementation will never reduce the capacity of its internal buffer. What if this is not ok? Just use shrink_to_fit()
, I thought:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
m_vector.reserve(whatever_input.size());
m_vector.shrink_to_fit(whatever_input.size()); // ← Here
populate(m_vector, whatever_input);
}
Oh, how nice that would be… But to my surprise, it doesn't compile, because shrink_to_fit()
doesn't take a number!
The way shrink_to_fit()
is supposed to be used, apparently, is that you fill the vector first. Then, you call shrink_to_fit()
, which is going to derive the capacity need from the number of elements in the vector after the fact, but that is clearly suboptimal if I could have told it that beforehand, because now, all that content has to be moved.
Objective: I would like a function vector_reserve_or_shrink()
to be used in this context:
void LongLived::reconfigure(const InputT& whatever_input)
{
m_vector.clear();
vector_reserve_or_shrink(m_vector, whatever_input.size()); // ← Implement this!
populate(m_vector, whatever_input);
}
I'm not actually obsessed with shaving every unused byte off the vector. Rather, I would be glad to leave it up to something implementation defined like shrink_to_fit()
, which might know something about the quirks of the allocator, and may choose to do nothing. This way, one doesn't risk making an abstraction inversion that negates any lower level optimizations. For example, say the allocator's granularity is 16: Then, the vector implementation might like to give you 15 bytes for free when you ask for one, for all I know, which would just be counterproductive to try to give back.
Use something like this:
template <typename VECTOR>
void setCapacity(VECTOR &vector, std::size_t capacity) {
if (capacity < vector.size()) capacity = vector.size();
if (vector.capacity() > capacity) {
VECTOR v;
v.reserve(capacity);
std::size_t size = vector.size();
for (std::size_t i = 0; i < size; i++) {
v.emplace_back(std::move(vector[i]));
}
vector.swap(v);
} else {
vector.reserve(capacity);
}
}
It sets vector capacity to capacity
(if possible), while retaining elements. And it only does one allocation.
template<class V>
void hard_reserve( V& v, std::size_t capacity ){
if (v.capacity() > capacity) v.shrink_to_fit();
v.reserve(capacity);
}
it isn't perfect in some corner cases, but those will be corner cases of an already corner case, and std
does not provide containers suitable for the many angled one.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With