简体   繁体   中英

Set *both* elements and initial capacity of std::vector

I've read in this post that the initial capacity of a std::vector couldn't be controlled by its constructor. The best way to set it, if you know its size will be constant at runtime, seems then to be:

const int size;
std::vector<T> v;
v.reserve(size);

But, since T is a big class, I'm happy to use the initialization argument of the constructor std::vector<T> v(size, T()); . What is then the best way to allocate only the memory I need without having to manually iterate over the elements to initialize them?

std::vector<T> v;
v.reserve(size);
for(T t : v) t = T();    // `has to call T::operator= (that may not even be defined)

or

std::vector<T> v(size, T());  // may allocate more memory than I need..
v.shrink_to_fit();            // ..then deallocate it immediately (pointless)

What would be the closest to my ideal:

std::vector<T> v(size, T(), /*capacity =*/ size);

[EDIT]: It turns out from your answers that what I need more exactly is v to be filled with size instances of T , each build with T 's default constructor, and not copied. How can I do since I can't use an initializer list when size isn't known at compile time?


Bonus: By the way, why is there no way choosing the initial capacity of a vector?

Bonus first:

Bonus: By the way, why is there no way choosing the initial capacity of a vector?

Because the designers of the class never considered it important enough to include.


I know of no implementation where after:

std::vector<T> v(size, T());

the following assert does not hold:

assert(v.capacity() == size);

It isn't guaranteed by the standard to hold. But it does appear to be the de-facto standard.

One can not however transfer this bit of lore to std::string .


If you want to positively require that capacity() == size() after construction, you could use shrink_to_fit() . That being said, shrink_to_fit() isn't guaranteed by the standard to work, and if it does do anything at all, it will temporarily double your memory requirements. Such use might look like:

std::vector<T> v(size, T());
if (v.capacity() > v.size())
    v.shrink_to_fit(); // If executed and successful, this will allocate a new buffer
if (v.capacity() > v.size())
    // now what?

Use of reserve() is no more effective. After reserve() , capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise.

It's not clear how you want all the elements constructed. Let's say you want the vector to have n elements and capacity desired_capacity , which may be equal to n .

If you want n elements identical, try this:

std::vector<T> v(n, t);  // creates n copies of the value t.
v.reserve(desired_capacity);

If you don't want to specify an initial value, you can omit that:

std::vector<T> v(n);     // creates n copies of T, default constructed.
v.reserve(desired_capacity);

If you have a certain number of elements you want to specify individually, try an initializer list.

std::vector({t1, t2, ..., tn});  // initializer list
v.reserve(desired_capacity);

The list of possibilities goes on; for more, see the vector::vector reference . Using "in-place" construction means you can populate a vector with types that are not copy-constructible.

Another option, to fill a vector with non-copyable types, is to use emplace_back repeatedly:

std::vector<T> v;
v.reserve(n); 
for (int i = 0; i < n; ++i)
{
   v.emplace_back(x, y, z);  // Construct T(x, y, z) in-place
}

One note about your original question: your for loop contains an error:

for(T t : v) t = T();    
// almost certainly want for (T& t : v) ...
// in order to get references to modify vector elements.

The vector constructors which specify the initial size all have to iterate to initialize the elements anyway. Even if you don't have code that explicitly iterates to initialize elements, that is what is happening inside the vector constructor. So I don't see the problem with doing:

template<typename T, typename... Args>
std::vector<T> create_vector_with_capacity(size_t size, size_t capacity, Args... args)
{
    std::vector<T> v;
    v.reserve(capacity);

    for (size_t i = 0; i != size; ++i)
        v.emplace_back( args... );

    return v;
}

Sample usage:

std::vector<std::string> vec = create_vector_with_capacity<std::string>(5, 20, "foo");

std::vector 's move semantics ensure that, even if copy-elision is disabled and T is not movable, the vector is not copied even though it is returned by value.

This version constructs each element in-place using args (or default constructor if you didn't provide any args); alternatively you could replace Args... args and emplace_back with T&& t = T() and push_back , which would copy-construct all elements from one (possibly temporary) that you pass in.

Note: Actually the standard does not absolutely guarantee that moving a vector retains its capacity (not that I'm aware of anyway); if your implementation doesn't do this , or if you really want to guarantee capacity retention, then instead of returning v , make it be a parameter passed by reference that the caller has to supply an empty vector for.

You may do:

std::vector<T> v;
v.reserve(size); // don't construct any T. v.size() == 0.
v.resize(size);  // do construct T.

you can build vector with capacity and initial value,

std::vector<std::string> vs(5, "abc");
std::vector<std::pair<int, int>> x(6, std::pair<int,int>(1, 2));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM