The University of Iowa
The College of Liberal Arts and Sciences
Department of Computer Science

Programming Languages and Tools:

CS:3210:0001

Lecture/Lab #23

Programming with C++

Iterator terminology/invalidation/pitfalls, a deeper look at std::vector

Warm up

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()

Iterator terminology

  • 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 terminology (cont.)

  • 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.

Avoiding undefined behavior

  • 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.

std::vector recap

  • 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.

Manipulating std::vector

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.

Iterator invalidation: C++ vs Python

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";

Python

C++

Made with Slides.com