COMP6771 Week 7.2
Custom Iterators
Iterator revision
- Iterator is an abstract notion of a pointer
- Iterators are types that abstract container data as a sequence of objects
- The glue between containers and algorithms
- Designers of algorithms don't care about details about data structures
- Designers of data structures don't have to provide extensive access operations
- The glue between containers and algorithms
std::vector v{1, 2, 3, 4, 5};
++(*v.begin()); // vector<int>'s non-const iterator
*v.begin(); // vector<int>'s const iterator
v.cbegin(); // vector<int>'s const iterator
Iterator invalidation
- Iterator is an abstract notion of a pointer
- What happens when we modify the container?
- What happens to iterators?
- What happens to references to elements?
- Using an invalid iterator is undefined behaviour
std::vector v{1, 2, 3, 4, 5};
// Copy all 2s
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 2) {
v.push_back(2);
}
}
// Erase all 2s
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 2) {
v.erase(it);
}
}
Iterator invalidation - push_back
- Think about the way a vector is stored
- "If the new size() is greater than capacity() then all iterators and references (including the past-the-end iterator) are invalidated. Otherwise only the past-the-end iterator is invalidated."
std::vector v{1, 2, 3, 4, 5};
// Copy all 2s
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 2) {
v.push_back(2);
}
}
Iterator invalidation - erase
- "Invalidates iterators and references at or after the point of the erase, including the end() iterator."
- For this reason, erase returns a new iterator
std::vector v{1, 2, 3, 4, 5};
// Erase all even numbers (C++11 and later)
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it);
} else {
++it;
}
}
Iterator invalidation - general
- Containers generally don't invalidate when you modify values
- But they may invalidate when removing or adding elements
- std::vector invalidates everything when adding elements
- std::unordered_(map/set) invalidates everything when adding elements
- std::map/set does not invalidate iterators upon insertion (why?)
Iterator traits
- Each iterator has certain properties
- Category (input, output, forward, bidirectional, random-access)
- Value type (T)
- Reference Type (T& or const T&)
- Pointer Type (T* or T* const)
- Not strictly required
- Difference Type (type used to count how far it is between iterators)
- When writing your own iterator, you need to tell the compiler what each of these are
Iterator requirements
A custom iterator class should look, at minimum, like this
class Iterator {
public:
using iterator_category = std::forward_iterator_tag;
using value_type = T;
using reference = T&;
using pointer = T*; // Not strictly required, but nice to have.
using difference_type = int;
reference operator*() const;
Iterator& operator++();
Iterator operator++(int) {
auto copy{*this};
++(*this);
return copy;
}
// This one isn't strictly required, but it's nice to have.
pointer operator->() const { return &(operator*()); }
friend bool operator==(const Iterator& lhs, const Iterator& rhs) { ... };
friend bool operator!=(const Iterator& lhs, const Iterator& rhs) { return !(lhs == rhs); }
};
Container requirements
- All a container needs to do is to allow std::[cr]begin / std::[cr]end
- This allows use in range-for loops, and std algorithms
- Easiest way is to define begin/end/cbegin/cend methods
- By convention, we also define a type Container::[const_]iterator
class Container {
// Make the iterator using one of these by convention.
class iterator {...};
using iterator = ...;
// Need to define these.
iterator begin();
iterator end();
// If you want const iterators (hint: you do), define these.
const_iterator begin() const { return cbegin(); }
const_iterator cbegin() const;
const_iterator end() const { return cend(); }
const_iterator cend() const;
};
Dissecting IntStack
- The iterator traits
- The overloaded operators (*, ->)
- The equality operators
- The constructor (default to nullptr)
- The private data
- The iterator is defined inside the class, so gets access to private data
- Iterator defines the container as a friend class for the constructors
- Key points in the List Class:
- begin() – returns an Iterator object
- end() – returns an Iterator object (with nullptr as private data)
- Note: The Iterator Class does not modify the List/Node data except through returning references.
Custom bidirectional iterators
- Need to define operator--() on your iterator
- Need to move from c.end() to the last element
- c.end() can't just be nullptr
- Need to move from c.end() to the last element
- Need to define the following on your container:
class Container {
// Make the iterator
class reverse_iterator {...};
// or
using reverse_iterator = ...;
// Need to define these.
reverse_iterator rbegin();
reverse_iterator rend();
// If you want const reverse iterators (hint: you do), define these.
const_reverse_iterator rbegin() const { return crbegin(); }
const_reverse_iterator crbegin();
const_reverse_iterator rend() const { return crend(); }
const_reverse_iterator crend() const;
};
Automatic reverse iterators
- Reverse iterators can be created by std::reverse_iterator
- Requires a bidirectional iterator
- You should be able to just copy-and-paste the following code
class Container {
// Make the iterator using these.
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// Need to define these.
reverse_iterator rbegin() { return reverse_iterator{end()}; }
reverse_iterator rend() { return reverse_iterator{begin()}; }
// If you want const reverse iterators (hint: you do), define these.
const_reverse_iterator rbegin() const { return crbegin(); }
const_reverse_iterator rend() const { return crend(); }
const_reverse_iterator crbegin() const { return const_reverse_iterator{cend()}; }
const_reverse_iterator crend() const { return const_reverse_iterator{cbegin()}; }
};
Automatic reverse iterators
- Reverse iterators can be created by std::reverse_iterator
- rbegin() stores end(), so *rbegin is actually *(--end())
Random access iterators
Text
class Iterator {
...
using reference = T&;
using difference_type = int;
Iterator& operator+=(difference_type rhs) { ... }
Iterator& operator-=(difference_type rhs) { return *this += (-rhs); }
reference operator[](difference_type index) { return *(*this + index); }
friend Iterator operator+(const Iterator& lhs, difference_type rhs) {
Iterator copy{*this};
return copy += rhs;
}
friend Iterator operator+(difference_type lhs, const Iterator& rhs) { return rhs + lhs; }
friend Iterator operator-(const Iterator& lhs, difference_type rhs) { return lhs + (-rhs); }
friend difference_type operator-(const Iterator& lhs, const Iterator& rhs) { ... }
friend bool operator<(Iterator lhs, Iterator rhs) { return rhs - lhs > 0; }
friend bool operator>(Iterator lhs, Iterator rhs) { return rhs - lhs < 0; }
friend bool operator<=(Iterator lhs, Iterator rhs) { !(lhs > rhs); }
friend bool operator>=(Iterator lhs, Iterator rhs) { !(lhs < rhs); }
}
See legacy requirements for random access iterators
COMP6771 19T2 - 7.2 - Custom Iterators
By cs6771
COMP6771 19T2 - 7.2 - Custom Iterators
- 839