35

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.

Community
  • 1
  • 1
P45 Imminent
  • 7,466
  • 2
  • 30
  • 69
  • 21
    @davidhigh: yes it would answer the question for my particular compiler and STL implementation but I could also answer questions like "what does `i = i++` do" in a similar way. – P45 Imminent Jun 17 '14 at 09:56
  • Very nice question, +1. I can't find anything straightforward about it in the standard; something might follow from the dark magic of allocator requirements, though. – Angew is no longer proud of SO Jun 17 '14 at 10:04
  • I know it dumps the entire target buffer if the source's allocator is different than the targets. I can't comment on whether it honors current reserve if they utilize the *same* allocator without referring to the standard. (nice question, btw). – WhozCraig Jun 17 '14 at 10:07
  • Still it would be nice to know already for your compiler and STL implementation what the result is? If the standard is not immediately clear on it a series of evidences would be the only thing left. Anyway isn't the assignment the same as a copy internally? – Trilarion Jun 17 '14 at 11:20
  • 1
    In MSVC it seems to keep the correct reserve unless littleVector has more than 100 objects. – Mike Jun 17 '14 at 11:25
  • Have a look at [std::vector resize downward](http://stackoverflow.com/questions/1155693/stdvector-resize-downward) which seems to indicate that capacity of a vector never shrinks? – Trilarion Jun 17 '14 at 11:28
  • 2
    My compiler (msvc12) keeps the capacity at 100. – P45 Imminent Jun 17 '14 at 11:36
  • 2
    The `g++` implementation explicitly documents that the capacity is __not__ copied; also, `assign` is explicitly requested by the standard to be equivalent to an `erase` followed by an `insert` (which aren't influenced by the source `capacity`). OTOH, I found no place in the standard where `operator=` is mandated to behave in the same way as `assign`. – Matteo Italia Jun 17 '14 at 11:41

3 Answers3

19

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());
ecatmur
  • 137,771
  • 23
  • 263
  • 343
  • Capacity remarks restrict the behavior of insertions. Arguably container assignment doesn't fall into the category of insertions. – Ben Voigt Jun 17 '14 at 17:18
  • @BenVoigt sequence container copy assignment is specified to require `CopyAssignable`. It seems pretty clear that non allocator modifying copy assignment must result in reallocation if initial capacity is insufficient. – ecatmur Jun 17 '14 at 20:02
  • I agree. But I don't know if a pedant would call that operation "insertion" and apply the above rule. – Ben Voigt Jun 17 '14 at 22:39
  • I've begun a discussion on the ISO C++ forum: https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/j6o0qfaUX2c – ecatmur Jun 25 '14 at 00:08
9

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.

Sebastian Redl
  • 61,331
  • 8
  • 105
  • 140
6

This depends on the allocator traits.

Here's an excerpt from http://en.cppreference.com/w/cpp/container/vector/operator%3D:

If std::allocator_traits::propagate_on_container_copy_assignment() is true, the target allocator is replaced by a copy of the source allocator. If the target and the source allocators do not compare equal, the target (*this) allocator is used to deallocate the memory, then other's allocator is used to allocate it before copying the elements.(since C++11)

Basically, the memory is reallocated with the new allocator, if the allocators are incompatible (if they cannot deallocate each-other's memory.

It shouldn't matter between vector implementations, but between allocator implementations (which makes sense).

utnapistim
  • 24,817
  • 3
  • 41
  • 76
  • Do you have a normative reference (i.e. from the standard) for that claim? – ecatmur Jun 17 '14 at 12:49
  • 2
    While this is correct, it doesn't answer whether deallocation also occurs when the allocators are the same, or do not propagate on container copy assignment. – Ben Voigt Jun 17 '14 at 13:18
  • @ecatmur: 23.2.1p7 discussed allocator replacement. The moment of allocator replacement is the last time the container is allowed to use the old allocator for anything; all future operations are defined in terms of the replacement allocator. Therefore it must deallocate its old buffer at this time. – Ben Voigt Jun 17 '14 at 13:20
  • @BenVoigt sure, assuming they're unequal. – ecatmur Jun 17 '14 at 14:40