Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement an STL-style iterator and avoid common pitfalls?

I made a collection for which I want to provide an STL-style, random-access iterator. I was searching around for an example implementation of an iterator but I didn't find any. I know about the need for const overloads of [] and * operators. What are the requirements for an iterator to be "STL-style" and what are some other pitfalls to avoid (if any)?

Additional context: This is for a library and I don't want to introduce any dependency on it unless I really need to. I write my own collection to be able to provide binary compatibility between C++03 and C++11 with the same compiler (so no STL which would probably break).

like image 536
Tamás Szelei Avatar asked Nov 08 '11 17:11

Tamás Szelei


People also ask

What is an STL iterator?

An iterator is used to point to the memory address of the STL container classes. For better understanding, you can relate them with a pointer, to some extent. Iterators act as a bridge that connects algorithms to STL containers and allows the modifications of the data present inside the container.

How do STL iterators work?

An iterator is an object that can navigate over elements of STL containers. All iterator represents a certain position in a container. To make it work, iterators have the following basic operations which are exactly the interface of ordinary pointers when they are used to iterator over the elements of an array.

What is the strongest iterator that list provides?

Random-Access Iterators: They are the most powerful iterators. They are not limited to moving sequentially, as their name suggests, they can randomly access any element inside the container.


1 Answers

http://www.cplusplus.com/reference/std/iterator/ has a handy chart that details the specs of § 24.2.2 of the C++11 standard. Basically, the iterators have tags that describe the valid operations, and the tags have a hierarchy. Below is purely symbolic, these classes don't actually exist as such.

iterator {     iterator(const iterator&);     ~iterator();     iterator& operator=(const iterator&);     iterator& operator++(); //prefix increment     reference operator*() const;     friend void swap(iterator& lhs, iterator& rhs); //C++11 I think };  input_iterator : public virtual iterator {     iterator operator++(int); //postfix increment     value_type operator*() const;     pointer operator->() const;     friend bool operator==(const iterator&, const iterator&);     friend bool operator!=(const iterator&, const iterator&);  }; //once an input iterator has been dereferenced, it is  //undefined to dereference one before that.  output_iterator : public virtual iterator {     reference operator*() const;     iterator operator++(int); //postfix increment }; //dereferences may only be on the left side of an assignment //once an output iterator has been dereferenced, it is  //undefined to dereference one before that.  forward_iterator : input_iterator, output_iterator {     forward_iterator(); }; //multiple passes allowed  bidirectional_iterator : forward_iterator {     iterator& operator--(); //prefix decrement     iterator operator--(int); //postfix decrement };  random_access_iterator : bidirectional_iterator {     friend bool operator<(const iterator&, const iterator&);     friend bool operator>(const iterator&, const iterator&);     friend bool operator<=(const iterator&, const iterator&);     friend bool operator>=(const iterator&, const iterator&);      iterator& operator+=(size_type);     friend iterator operator+(const iterator&, size_type);     friend iterator operator+(size_type, const iterator&);     iterator& operator-=(size_type);       friend iterator operator-(const iterator&, size_type);     friend difference_type operator-(iterator, iterator);      reference operator[](size_type) const; };  contiguous_iterator : random_access_iterator { //C++17 }; //elements are stored contiguously in memory. 

You can either specialize std::iterator_traits<youriterator>, or put the same typedefs in the iterator itself, or inherit from std::iterator (which has these typedefs). I prefer the second option, to avoid changing things in the std namespace, and for readability, but most people inherit from std::iterator.

struct std::iterator_traits<youriterator> {             typedef ???? difference_type; //almost always ptrdiff_t     typedef ???? value_type; //almost always T     typedef ???? reference; //almost always T& or const T&     typedef ???? pointer; //almost always T* or const T*     typedef ???? iterator_category;  //usually std::forward_iterator_tag or similar }; 

Note the iterator_category should be one of std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tag, or std::random_access_iterator_tag, depending on which requirements your iterator satisfies. Depending on your iterator, you may choose to specialize std::next, std::prev, std::advance, and std::distance as well, but this is rarely needed. In extremely rare cases you may wish to specialize std::begin and std::end.

Your container should probably also have a const_iterator, which is a (possibly mutable) iterator to constant data that is similar to your iterator except it should be implicitly constructable from a iterator and users should be unable to modify the data. It is common for its internal pointer to be a pointer to non-constant data, and have iterator inherit from const_iterator so as to minimize code duplication.

My post at Writing your own STL Container has a more complete container/iterator prototype.

like image 55
Mooing Duck Avatar answered Sep 24 '22 00:09

Mooing Duck