tl;dr : Yes, moving a std::vector<T, A>
possibly invalidates the iterators
The common case (with std::allocator
in place) is that invalidation does not happen but there is no guarantee and switching compilers or even the next compiler update might make your code behave incorrect if you rely on the fact the your implementation currently does not invalidate the iterators.
On move assignment:
The question whether std::vector
iterators can actually remain valid after move-assignment is connected with the allocator awareness of the vector template and depends on the allocator type (and possibly the respective instances thereof).
In every implementation I have seen, move-assignment of a std::vector<T, std::allocator<T>>
1 will not actually invalidate iterators or pointers. There is a problem however, when it comes down to making use of this, as the standard just cannot guarantee that iterators remain valid for any move-assignment of a std::vector
instance in general, because the container is allocator aware.
Custom allocators may have state and if they do not propagate on move assignment and do not compare equal, the vector must allocate storage for the moved elements using its own allocator.
Let:
std::vector<T, A> a{/*...*/};
std::vector<T, A> b;
b = std::move(a);
Now if
std::allocator_traits<A>::propagate_on_container_move_assignment::value == false &&
std::allocator_traits<A>::is_always_equal::value == false &&
(possibly as of c++17)
a.get_allocator() != b.get_allocator()
then b
will allocate new storage and move elements of a
one by one into that storage, thus invalidating all iterators, pointers and references.
The reason is that fulfillment of above condition 1. forbidds move assignment of the allocator on container move. Therefore, we have to deal with two different instances of the allocator. If those two allocator objects now neither always compare equal (2.) nor actually compare equal, then both allocators have a different state. An allocator x
may not be able to deallocate memory of another allocator y
having a different state and therefore a container with allocator x
cannot just steal memory from a container which allocated its memory via y
.
If the allocator propagates on move assignment or if both allocators compare equal, then an implementation will very likely choose to just make b
own a
s data because it can be sure to be able to deallocate the storage properly.
1: std::allocator_traits<std::allocator<T>>::propagate_on_container_move_assignment
and std::allocator_traits<std::allocator<T>>::is_always_equal
both are typdefs for std::true_type
(for any non-specialized std::allocator
).
On move construction:
std::vector<T, A> a{/*...*/};
std::vector<T, A> b(std::move(a));
The move constructor of an allocator aware container will move-construct its allocator instance from the allocator instance of the container which the current expression is moving from. Thus, the proper deallocation capability is ensured and the memory can (and in fact will) be stolen because move construction is (except for std::array
) bound to have constant complexity.
Note: There is still no guarantee for iterators to remain valid even for move construction.
On swap:
Demanding the iterators of two vectors to remain valid after a swap (now just pointing into the respective swapped container) is easy because swapping only has defined behaviour if
std::allocator_traits<A>::propagate_on_container_swap::value == true ||
a.get_allocator() == b.get_allocator()
Thus, if the allocators do not propagate on swap and if they do not compare equal, swapping the containers is undefined behaviour in the first place.