Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does a vector assignment invalidate the `reserve`?

Suppose I write

std::vector<T> littleVector(1); std::vector<T> bigVector;  bigVector.reserve(100); bigVector = littleVector; 

Does the standard say that bigVector will still have 100 elements reserved? Or would I experience memory reallocation if I were to push_back 99 elements? Perhaps it even varies between STL implementations.

This was previously discussed here but no Standard references were given.

like image 926
P45 Imminent Avatar asked Jun 17 '14 09:06

P45 Imminent


People also ask

Does vector insert reserve?

If the allocated memory capacity in the vector is large enough to contain the new elements, no additional allocations for the vector are needed. So no, then it won't reserve memory.

Does Reserve change vector size?

reserve() does not change the size of the vector. If new_cap is greater than capacity(), all iterators, including the past-the-end iterator, and all references to the elements are invalidated. Otherwise, no iterators or references are invalidated.

What is vector Reserve?

std::vector::reserveRequests that the vector capacity be at least enough to contain n elements. If n is greater than the current vector capacity, the function causes the container to reallocate its storage increasing its capacity to n (or greater).

Does vector Reserve allocate memory?

vector::reserve does allocate memory, so your question about reserving memory without allocating is incorrect. The point is that reserving memory can be done without changing the vectors size. Basically a vector has two sizes, it's size and it's capacity.


2 Answers

Unfortunately, the standard underspecifies behavior on allocator-aware sequence container assignment, and indeed is strictly speaking inconsistent.

We know (from Table 28 and from 23.2.1p7) that if allocator_traits<allocator_type>::propagate_on_container_copy_assignment::value is true then the allocator is replaced on copy assignment. Further, from Tables 96 and 99 we find that the complexity of copy assignment is linear, and the post-condition on operation a = t is that a == t, i.e. (Table 96) that distance(a.begin(), a.end()) == distance(t.begin(), t.end()) && equal(a.begin(), a.end(), t.begin()). From 23.2.1p7, after copy assignment, if the allocator propagates, then a.get_allocator() == t.get_allocator().

With regard to vector capacity, 23.3.6.3 [vector.capacity] has:

5 - Remarks: Reallocation invalidates all the references, pointers, and iterators referring to the elements in the sequence. It is guaranteed that no reallocation takes place during insertions that happen after a call to reserve() until the time when an insertion would make the size of the vector greater than the value of capacity().

If we take library DR341 as a guide to reading the Standard:

However, the wording of 23.3.6.3 [vector.capacity]paragraph 5 prevents the capacity of a vector being reduced, following a call to reserve(). This invalidates the idiom, as swap() is thus prevented from reducing the capacity. [...]

DR341 was resolved by adding paragraphs into 23.3.6.3:

void swap(vector<T,Allocator>& x);
7 - Effects: Exchanges the contents and capacity() of *this with that of x.
8 - Complexity: Constant time.

The conclusion is that from the point of view of the Library committee, operations only modify capacity() if mentioned under 23.3.6.3. Copy assignment is not mentioned under 23.3.6.3, and thus does not modify capacity(). (Move assignment has the same issue, especially considering the proposed resolution to Library DR2321.)

Clearly, this is a defect in the Standard, as copy assignment propagating unequal allocators must result in reallocation, contradicting 23.3.6.3p5.

We can expect and hope this defect to be resolved in favour of:

  • non-reduced capacity() on non-allocator-modifying copy assignment;
  • unspecified capacity() on allocator-modifying copy assignment;
  • non-reduced capacity() on non-allocator-propagating move assignment;
  • source-container capacity() on allocator-propagating move assignment.

However, in the current situation and until this is clarified you would do well not to depend on any particular behavior. Fortunately, there is a simple workaround that is guaranteed not to reduce capacity():

bigVector.assign(littleVector.begin(), littleVector.end()); 
like image 183
ecatmur Avatar answered Nov 05 '22 05:11

ecatmur


The only requirement on operator= for standard containers is that afterwards, src == dst, as specified in Table 96 (in 23.2, General Container Requirements). Furthermore, the same table specifies the meaning of operator ==:

distance(lhs.begin(), lhs.end()) == distance(rhs.begin(), rhs.end()) // same size   && equal(lhs.begin(), lhs.end(), rhs.begin()) // element-wise equivalent 

Note that this doesn't include capacity in any way. Nor does any other part of the standard mention capacity beyond the general invariant that capacity() >= size(). The value of capacity after assignment is therefore unspecified, and the container is free to implement assignment whichever way it wants, as long as allocator requirements are kept.


In general, you will find that implementations behave such that

  • if the allocators compare equal and dst has sufficient capacity, it will retain its old storage,
  • otherwise it will allocate just enough storage for the new elements, and
  • in no case will care what the capacity of src is.

Of course, move assignment is a different story. Since it is generally implemented by stealing the source storage, the capacity will be taken as well.

like image 40
Sebastian Redl Avatar answered Nov 05 '22 04:11

Sebastian Redl