Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Right way to "reserve or shrink" a vector to a known future capacity need

Tags:

c++

stdvector

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.

like image 752
user2394284 Avatar asked Dec 11 '17 22:12

user2394284


2 Answers

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.

like image 51
geza Avatar answered Sep 18 '22 02:09

geza


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.

like image 27
Yakk - Adam Nevraumont Avatar answered Sep 18 '22 02:09

Yakk - Adam Nevraumont