What triggered this question is some code along the line of:
std::vector<int> x(500);
std::vector<int> y;
std::swap(x,y);
And I was wondering if swapping the two requires twice the amount of memory that x
needs.
On cppreference I found for std::vector::swap
(which is the method that the last line effectively calls):
Exchanges the contents of the container with those of other. Does not invoke any move, copy, or swap operations on individual elements.
All iterators and references remain valid. The past-the-end iterator is invalidated.
And now I am more confused than before. What does std::vector::swap
actually do when it does not move, copy or swap elements?
How is it possible that iterators stay valid?
Does that mean that something like this is valid code?
std::vector<int> x(500);
std::vector<int> y;
auto it = x.begin();
std::swap(x,y);
std::sort(it , y.end()); // iterators from different containers !!
vector
internally stores (at least) a pointer to the actual storage for the elements, a size and a capacity.† std::swap
just swaps the pointers, size and capacity (and ancillary data if any) around; no doubling of memory or copies of the elements are made because the pointer in x
becomes the pointer in y
and vice-versa, without any new memory being allocated.
The iterators for vector
are generally lightweight wrappers around pointers to the underlying allocated memory (that's why capacity changes generally invalidate iterators), so iterators produced for x
before the swap
seamlessly continue to refer to y
after the swap
; your example use of sort
is legal, and sorts y
.
If you wanted to swap the elements themselves without swapping storage (a much more expensive operation, but one that leaves preexisting iterators for x
refering to x
), you could use std::swap_range
, but that's a relatively uncommon use case. The memory usage for that would depend on the implementation of swap
for the underlying object; by default, it would often involve a temporary, but only for one of the objects being swapped at a time, not the whole of one vector
.
† Per the comments, it could equivalently use pointers to the end of the used space and the end of the capacity, but either approach is logically equivalent, and just microoptimizes in favor of slightly different expected use cases; storing all pointers optimizes for use of iterators (a reasonable choice), while storing size_type
optimizes for .size()
/.capacity()
calls.