I understand how random access iterators work for contiguous containers like std::vector
: the iterator simply maintains a pointer to the current element and any additions/subtractions are applied to the pointer.
However, I'm baffled as to how similar functionality could be implemented for a non-contiguous container. My first guess for how std::deque:iterator
works, is that it maintains a pointer to some table of the groups of contiguous memory it contains, but I'm not sure.
How would a typical standard library implement this?
The containers eque<> and Vector<>, as well as ordinary arrays, support random access iterators.
All STL containers do not support all 5 types of iterators. For instance, random-access iterators are supported by the container “vector”, whereas bidirectional iterators are supported by the container “list”.
Bidirectional iterators are the iterators used to access the elements in both the directions, i.e., towards the end and towards the beginning. A random access iterator is also a valid bidirectional iterator. Many containers implement the bidirectional iterator such as list, set, multiset, map, multimap.
C++ Iterators Reverse Iterators A reverse iterator is made from a bidirectional, or random access iterator which it keeps as a member which can be accessed through base() . To iterate backwards use rbegin() and rend() as the iterators for the end of the collection, and the start of the collection respectively.
A deque iterator can be implemented by storing both a pointer to the referenced value and a double pointer to the contiguous block of memory in which that value is located. The double pointer points into a contiguous array of pointers to blocks managed by the deque.
class deque_iterator
{
T* value;
T** block;
…
}
Because both value
and block
point into contiguous memory, you can implement operations such finding the distance between iterators in constant time (example adapted from libc++).
difference_type operator-(deque_iterator const& x, deque_iterator const& y)
{
return (x.block - y.block) * block_size
+ (x.value - *x.block)
- (y.value - *y.block);
}
Note that, while value
will not be invalidated by operations such as push_front
and push_back
, block
might be, which is why deque_iterator
is invalidated by such operations.
You can satisfy the requirememts of a std::deque
with a std::vector<std::unique_ptr<std::array<T,N>>>
roughly. plus a low/high water mark telling you where the first/last elements are. (for an implementation defined N that could vary with T
, and the std::array
s are actually blocks of properly aligned uninitialized memory and not std::array
s, but you get the idea).
Use usual exponential growth, but on both front and back.
Lookup simply does (index+first)/N
and %N
to find the block and sub element.
This is more expensive than a std::vector
lookup, but is O(1).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With