| # Iterators |
| |
| So far, examples that have been presented have shown many uses of `fbl::` |
| iterators. Iterators in `fbl::` use an API very similar to the API used in the |
| `std::` containers, so hopefully this will feel very familiar to you. It is, |
| however, worth taking a small amount of time to mention the things that all |
| `fbl::` iterators support, in addition to places where they differ slightly from |
| `std::` iterators. |
| |
| ## `iterator` and `const_iterator` |
| |
| As with `std::` iterators, `fbl::` iterators come in two flavors, a non-constant |
| and a constant version. Operations such as `begin()` or `find()` will return a |
| simple iterator in the case that the reference to the container the user has is |
| non-`const`, and a `const_iterator` otherwise. Dereference operations performed |
| on `const_iterators` give a `const T&` and therefore `const` access to the |
| underlying object. |
| |
| ## `begin()`/`end()` |
| |
| Just like in `std::`, the `begin()` method on a container returns an iterator to |
| the first element in the container while `end()` returns an iterator to an |
| element one past the last element in the container. Both begin and end will |
| automatically return a `const_iterator` when called on a `const` reference to |
| the container, but `cbegin()`/`cend()` may be used in the case that a |
| `const_iterator` is explicitly desired from a mutable reference to the |
| container. |
| |
| ## Iterator comparison and default initialized iterators vs. `end()` |
| |
| Like `std::`, all fbl:: iterators support testing for equality using the `==` |
| and `!=` operators. Unlike `std::`, none of the iterators have random access |
| iterator semantics, and the `>`, `>=`, `<`, and `<=` operators are not supported |
| for any of the `fbl::` containers' iterator types. |
| |
| In addition, there is a slight internal difference between a default initialized |
| iterator and an iterator returned from a call to a container's `end()` method. |
| Semantically, they are the same. Both a default initialized iterator and the |
| value of `end()` are invalid, so comparison between the two will return `true`. |
| The bits contained in the two instances, however are different. Always use the |
| comparison operators when testing for equality between two iterators. |
| |
| ```cpp |
| fbl::DoublyLinkedList<Obj*> the_list; |
| fbl::DoublyLinkedList<Obj*>::iterator default_init; |
| auto end_init = the_list.end(); |
| |
| if (default_init == end_init) { } // This comparison is true |
| if (!memcmp(&default_init, &end_init, sizeof(end_init))) { } // This is not. |
| ``` |
| |
| ## Iterator advancement |
| |
| All iterators support both the pre and post-fix forms of the `++` operator. The |
| pre-fix form will move the iterator to the next element in the sequence, and |
| return a copy of the iterator now pointing to the next element. The post-fix |
| form will move the iterator to the next element in the sequence while returning |
| a copy of the pre-advanced iterator. |
| |
| ```cpp |
| // Assuming that the list starts containing objects with values |
| // "5 7 9", in that order. |
| fbl::DoublyLinkedList<Obj*> the_list; |
| auto iter = the_list.begin(); // iter points to "5". |
| auto second = iter++; // iter points to "7", but second points to "5" |
| auto third = ++iter; // iter points to "9", and so does third |
| ++iter; // iter is now equal to the_list.end() |
| ``` |
| |
| Iterators for `DoublyLinkedList`s, `HashTable`s with doubly linked list buckets, |
| and `WAVLTree`s all support bidirectional iteration via the `--` operator, again |
| in both its pre and post fix forms. `SinglyLinkedList`s and `HashTable`s with |
| singly linked list buckets do not. |
| |
| Advancing an iterator past the end of a container gives `container.end()`. |
| Attempting to advance further is legal, but does not change the value of the |
| iterator. Backing up a bi-directional iterator that is currently set to |
| `container.end()` using the `--` operator will produce an iterator that points |
| to the last element in the list, _however backing up an iterator that has been |
| default initialized does not_. Instead, executing either `++` or `--` on a |
| default initialized leaves the iterator in the default initialized state. |
| Finally, backing up an iterator whose value is equal to `container.begin()` will |
| produce an iterator whose value is equal to `container.end()`. Subsequent |
| applications of `--` will walk through the elements in reverse order starting |
| with the last element. |
| |
| ## Dereferencing iterators |
| |
| Elements in `fbl::` containers are always objects, therefore they always support |
| the `->` operator in addition to the unary `*` operator. Both produce either a |
| `T&` or a `const T&` (which `->` then accesses a member of) based on if the |
| iterator was a `const_iterator` or not. |
| |
| It is illegal to attempt to deference an invalid iterator and will either |
| trigger a `ZX_DEBUG_ASSERT` or undefined behavior, depending on the nature of |
| the build. |
| |
| ## Creating an iterator from an object using container::make_iterator() |
| |
| Because of the intrusive nature of the containers, it is possible to create a |
| container iterator using an existing reference to an object. For example, given |
| a tree of objects ordered by key, a function that takes an object, and returns a |
| reference to the object before it, in key sequence, could be written by saying |
| something like: |
| |
| ``` |
| using ObjectTree = fbl::WAVLTree<uint64_t, fbl::RefPtr<Object>>; |
| |
| fbl::RefPtr<Object> FetchPrior(ObjectTree& tree, Object& obj) { |
| ZX_DEBUG_ASSERT(obj.InContainer()); |
| auto iter = tree.make_iterator(obj); |
| return (--iter).IsValid() ? iter.CopyPointer() : nullptr; |
| } |
| ``` |
| |
| ## iterator::IsValid() |
| |
| All `fbl::` iterator instances may be tested for validity using the `IsValid` |
| method of the iterator instance itself. Testing an iterator for validity in this |
| way is equivalent to testing `iter != container.end()`, however the `IsValid` |
| approach may produce _slightly_ more efficient code, depending on how smart the |
| compiler is and how much visibility it has into the implementation of the |
| container's `end()` method.. |
| |
| ## iterator::CopyPointer() |
| |
| Finally, `fbl::` iterators provide a method called `CopyPointer`, which can be |
| used to produce a copy of the pointer type being used by the container. For |
| containers of raw pointers, this is nothing special. It is simply a `T*` copy of the |
| pointer to the object. In fact, `iter.CopyPointer() == &(*iter)` should always |
| be true for raw pointers. |
| |
| `CopyPointer` is not legal for managed pointers with unique semantics. Attempting |
| to call `CopyPointer` on a container of objects tracked using `std::unique_ptr` |
| will produce an error. |
| |
| Finally, when `CopyPointer` is executed on an iterator for a container of |
| copyable managed pointers, a new copy of the pointer will be produced using the |
| copy constructor of the pointer type. In other words, it will produce a new |
| managed reference to the object. |
| |
| Attempting to call `CopyPointer` on an invalid iterator to a copyable pointer |
| type will produce `nullptr`. |