The University of Iowa
The College of Liberal Arts and Sciences
Department of Computer Science
Lecture/Lab #23
Iterator terminology/invalidation/pitfalls, a deeper look at std::vector
std::vector<std::string> v =
{ "apple", "orange", "lemon", "grape", "banana" };
std::vector<std::string> v2 = v;
assert( v2.end() == v.end() ); // okay?
apple
v.end()
v
orange
lemon
grape
banana
v.begin()
apple
v2.end()
v2
orange
lemon
grape
banana
v2.begin()
Iterators for which the behavior of the expression *i is well-defined are called dereferenceable.
Past-the-end and singular iterators are not dereferenceable.
Invalidating an iterator means performing an operation after which the iterator becomes non-dereferenceable or singular.
Iterators that are not associated with any sequence are called singular. Except for assignment, the results of all operations on a singular iterator (including comparison for equality) are undefined.
// an example of a singular iterator
std::vector<std::string>::iterator i;
std::vector<std::string> v;
// `i` is *not* dereferencable
auto i = v.begin();
std::vector<std::string> v = { "hey" };
auto i = v.begin();
v.clear(); // `i` is invalidated
Iterator is called mutable if it allows modifying the element it is pointing to; otherwise the iterator is said to be constant.
std::vector<int> v = { 42, 101 };
auto i = v.begin();
*i = 17; // `i` is mutable, okay
auto const& v2 = v;
auto i2 = v2.begin();
*i2 = 17; // compile-time error, `i2` is constant
auto i3 = v.cbegin();
*i3 = 17; // compile-time error, `i3` is constant
Note that `vector<int>::iterator const` is not the same as `vector<int>::const_iterator`
An iterator j is called reachable from an iterator i if and only if there is a finite sequence of applications of
the expression ++i that makes i == j. If j is reachable from i, they refer to elements of the same sequence.
Increment, dereference, compare or subtract singular iterators.
DO NOT:
Dereference non-dereferencable iterators.
Increment past-the-end iterators.
Increment/decrement iterators to advance outside of the [begin, end) range.
Decrement begin iterators.
Increment, dereference, compare or subtract invalidated iterators.
A linear collection of objects, all of which have the same type
v[0]
v[1]
v[v.size()-1]
...
v.capacity()
v.size()
Guarantees that the elements are located in a continuous block of memory.
Consequently, adding new elements to a vector might lead to reallocation of the vector's storage and copying/moving of the existing elements.
Reallocation of the vector's storage -> invalidation of all existing iterators.
v.reserve( n );
Increase v's capacity to at least n elements; if n > v.capacity(), all iterators are invalidated.
auto iter = v.erase( pos );
Removes the element at pos.
auto iter = v.erase( first, last );
Removes the elements in the range [first, last).
Invalidates iterators and references at or after the point of the erase, including the end() iterator.
auto iter = v.insert( pos, v );
Inserts element v before pos.
auto iter = v.insert( pos, first, last );
Inserts elements from range [first, last) before pos.
If the new size() is greater than capacity(), all iterators and references are invalidated.
numbers = [11, 11, 22, 33]
for n in numbers:
numbers.remove( n )
print( numbers ) # [11, 33]
std::vector<int> numbers =
{ 11, 11, 22, 33 };
auto iter = numbers.begin();
for ( ; iter != numbers.end(); ++iter )
numbers.erase( iter ); // UB
for ( auto n : numbers )
std::cout << n << "\n";