简体   繁体   中英

Create a simple forward iterator which automatically wraps at the “end” of a circular buffer

I've created a simple circular buffer by inheriting std::vector and overloading its operator[] to modulo the desired index with the vector size:

template <typename T>
class circvector : public std::vector<T> {
public:
    T& operator[](size_t index) { return *(this->data() + index%this->size()); };       // modulo index by vector size when accessing with [] operator
};

int main()
{
    circvector<int> buffer;                         // create a circvector
    buffer.resize(10);                              // resize it
    std::iota(buffer.begin(), buffer.end(), 0);     // fill with monotonically increasing integers

    for (int i = 0; i < buffer.size() * 2; i++)
        std::cout << buffer[i] << " ";              // access elements with [] beyond the circvector size is safe
}

which correctly produces the output:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

I would also like to create a "smart" forward input iterator which supports operator++ (increment) and operator* (dereference) and which automatically "wraps" when advancing past the last element in the vector back to the beginning of the underlying vector to which it's associated.

This hypothetical iterator would allow, for example, easier execution of some functions in the algorithm library (which often take iterators as arguments) on a subset of the circvector without adding logic to check for and then split into two separate calls when the desired subset spans the end->start wrap of the circvector.

I've been reading up on creating custom iterators but descriptions get pretty hairy with information that seemingly satisfies requirements I don't think I need. Add to that discussion of recent deprecation of std::iterator and I'm not sure where to even begin to create my own iterator, or associate it with my circvector class.

Can someone get me started with a minimally workable template to run with?

This class appears to achieve the desired behavior, though it inherits from the deprecated std::iterator class:


template <typename T>
class circvector: public std::vector<T> {
public:
    T& operator[](size_t index_) { return *(this->data() + index_%this->size()); };     // modulo index_ by vector size when accessing with [] operator

    class iterator; // forward declaration

    iterator begin() { return circvector<T>::iterator(*this, 0); }
    iterator end() { return circvector<T>::iterator(*this, this->size()); } // will be same as begin() due to modulo in iterator constructor initializer list

private:

    class iterator : public std::iterator<std::output_iterator_tag, T> {
    private:
        circvector<T>& container_;      // NOTE: ORDER MATTERS! // dependency injection
        size_t index_{ 0 };         // NOTE: ORDER MATTERS! // state of iterator

    public:
        T& operator*() const { return container_[index_]; }                                     // this uses the overloaded operator[] which includes modulo
        iterator& operator+(int N) { index_ = (index_ + N) % container_.size(); return *this; } // random increment by N
        iterator& operator++() { index_ = (index_ + 1) % container_.size(); return *this; }     // increment with modulo
        iterator operator++(int) { return ++(*this); }                                              // just calls prefix increment: operator++()
        bool operator!=(const iterator & right) const { return index_ != right.index_ % container_.size(); }
        bool operator==(const iterator & right) const { return index_ == right.index_ % container_.size(); }
        explicit iterator(circvector<T>& container, size_t index_ = 0) : container_(container), index_(index_ % container_.size()) {}       // constructor
    };
};

It was modified from https://lorenzotoso.wordpress.com/2016/01/13/defining-a-custom-iterator-in-c/

A test program is:

int main()
{
    circvector<int> buffer;
    buffer.assign({ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });

    auto start_offset{ 8 };
    auto end_offset{ start_offset + 5 };

    for (int i = 0; i < buffer.size(); i++) std::cout << buffer[i] << " ";
    std::cout << "\n";
    std::for_each(buffer.begin() + start_offset, buffer.begin() + end_offset, [](auto& i) { i = 42; });
    for (int i = 0; i < buffer.size(); i++) std::cout << buffer[i] << " ";
}

creating output:

0 1 2 3 4 5 6 7 8 9
42 42 42 3 4 5 6 7 42 42

The use of algorithms from begin() to end() of course no longer works, since begin()==end(). But you can operate on partial segments of the buffer using algorithms (like std::for_each as shown) as long as the length is 1 less than the full buffer size.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM