Part 10 already! There's quite a lot of detail involved in writing an STL-like container. Now we're going to delve into some of the more interesting stuff...I promised you iterators, and so iterators is what you'll get. Like the indexing operations we added in part 8, a circular buffer traditionally doesn't let you iterate through the contents. But where's the fun in that?
Different containers support different classes of iterator:
- An input iterator allows you to read values with forward movement. It can be incremented, compared, and deferenced.
- An output iterator also moves forwards, but allows you to write values rather than read them on the way.
- A forward iterator effectively combines the input and output iterators, allowing you to read or write values with forward movement.
- A bidirectional iterator adds the capability to move backwards; you can increment and decrement them.
- A random access iterator allows you to read and write with the ability to do pointer arithmentic and pointer-like comparisons of the iterators.
- A reverse iterator is a bidirectional or random iterator that moves through the container in the reverse direction.
First steps
The container must supply the following nested types: iterator and const_iterator. These can be typedefs or nested class declarations (for example, the most efficient iterator implementation for the std::vector is a typedef of a pointer). However, to start off with, we can simply construct a nested class inside circular_buffer.
Let's start off with the non-const iterator version.
Our most implementation is not quite as simple as a pointer, but not much more complex. We need only maintain an index into the buffer and a back-pointer to the parent circular_buffer class. The iterator can be implemented in terms of circular_buffer::operator[].// In declaration
class iterator;
iterator begin();
iterator end();
Like a container, and an allocator, an STL iterator must provide a number of nested type definitions for algorithms to be able to use them appropriately. These include:
- difference_type
- value_type
- pointer
- reference
- iterator_category
- the iterator category (these are tag types in the std namespace)
- the difference_type
- the pointer type
- the reference type
Here's our first stab at an implementation:
template <typename T, typename A>
class circular_buffer<T,A>::iterator
: public std::iterator<std::random_access_iterator_tag,
value_type,
size_type,
pointer,
reference>
{
public:
typedef circular_buffer<T> parent_type;
typedef typename parent_type::iterator self_type;
iterator(parent_type &parent, size_type index)
: parent(parent), index(index) {}
self_type &operator++()
{
++index;
return *this;
}
self_type operator++(int) // postincrement
{
self_type old(*this);
operator++();
return old;
}
self_type &operator--()
{
--index;
return *this;
}
self_type operator--(int) // postdecrement
{
self_type old(*this);
operator--();
return old;
}
reference operator*() { return parent[index]; }
pointer operator->() { return &(parent[index]); }
bool operator==(const self_type &other) const
{ return &parent == &other.parent && index == other.index; }
bool operator!=(const self_type &other) const
{ return !(other == *this); }
private:
parent_type &parent;
size_type index;
};
template <typename T, typename A>
typename circular_buffer<T,A>::iterator circular_buffer<T,A>::begin()
{
return iterator(*this, 0);
}
template <typename T, typename A>
typename circular_buffer<T,A>::iterator circular_buffer<T,A>::end()
{
// Remember: end() is one past the last element.
return iterator(*this, size());
}
Make sure that you understand the difference between preincrement and postincrement and how you overload each operator in a class declaration. Also make sure you know the difference between operator* and operator->.
Implementing reverse iterators
If we want to support reverse iteration, we must provide type definitions for the iterator and const_iterator counterparts: reverse_iterator and const_reverse_iterator.
Helpfully, the C++ standard library provides a useful implementation trick for reverse iteration: std::reverse_iterator. This class wraps a bidirectional/random forward iterator and adapts forward and backward movement to appropriate calls on the underlying forwards iterator.
We can use it like this:
typedef std::reverse_iterator<iterator> reverse_iterator;
reverse_iterator rbegin() { return reverse_iterator(end()); }
reverse_iterator rend() { return reverse_iterator(begin()); }
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
No comments:
Post a Comment