Как реализовать итератор в стиле STL и избежать распространенных ошибок?



Я сделал коллекцию, для которой я хочу предоставить итератор с произвольным доступом в стиле STL. Я искал вокруг пример реализации итератора, но я не нашел ни одного. Я знаю о необходимости постоянных перегрузок [] и * операторы. Каковы требования к итератору, чтобы быть "STL-стиль" и каковы некоторые другие подводные камни, чтобы избежать (если таковые имеются)?



дополнительный контекст: это для библиотеки, и я не хочу вводить какую-либо зависимость от нее, если я действительно нужно. Я пишу свою собственную коллекцию, чтобы иметь возможность обеспечить двоичную совместимость между C++03 и C++11 с тем же компилятором (так что нет STL, который, вероятно, сломается).

733   6  

6 ответов:

http://www.cplusplus.com/reference/std/iterator/ имеет удобную диаграмму, которая детализирует спецификации § 24.2.2 стандарта C++11. В основном, итераторы имеют теги, которые описывают допустимые операции, и теги имеют иерархию. Ниже чисто символически, эти классы фактически не существуют как таковые.

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;
};

вы можете либо специализируются std::iterator_traits<youriterator>, или поставить те же typedefs в самом итераторе, или наследовать от std::iterator (который имеет эти typedefs). Я предпочитаю второй вариант, чтобы не менять вещи в std пространство имен, и для удобства чтения, но большинство людей наследуют от 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
};

обратите внимание, что iterator_category должен быть одним из std::input_iterator_tag,std::output_iterator_tag,std::forward_iterator_tag,std::bidirectional_iterator_tag или std::random_access_iterator_tag, в зависимости от того, какие требования удовлетворяет ваш итератор. В зависимости от вашего итератора, вы можете выбрать специализацию std::next,std::prev,std::advance и std::distance также, но это редко требуется. В чрезвычайно редкий случаи, которые вы можете специализироваться std::begin и std::end.

ваш контейнер, вероятно, также должен иметь const_iterator, который является (возможно, изменяемым) итератором для постоянных данных, похожих на ваш iterator за исключением того, что он должен быть неявно конструктивным из A iterator и пользователи должны быть в состоянии изменить данные. Обычно его внутренний указатель является указателем на непостоянные данные и имеет iterator наследовать от const_iterator таким образом, чтобы минимизировать код дублирование.

мой пост написание собственного контейнера STL имеет более полный прототип контейнера/итератора.

на документация iterator_facade от наддува.Итератор обеспечивает то, что выглядит как хороший учебник по реализации итераторов для связанного списка. Не могли бы вы использовать это в качестве отправной точки для построения итератора произвольного доступа над вашим контейнером?

Если ничего больше, вы можете взглянуть на функции-члены и typedefs, предоставляемые iterator_facade и использовать его в качестве отправной точки для создания собственного.

Томас Беккер написал полезную статью на эту тему здесь.

был также Этот (возможно, более простой) подход, который появился ранее на SO:Как правильно реализовать пользовательские итераторы и const_iterators?

вот пример итератора необработанного указателя.

вы не должны использовать итератор для работы с сырыми указателями!

#include <iostream>
#include <vector>
#include <list>
#include <iterator>
#include <assert.h>

template<typename T>
class ptr_iterator
    : public std::iterator<std::forward_iterator_tag, T>
{
    typedef ptr_iterator<T>  iterator;
    pointer pos_;
public:
    ptr_iterator() : pos_(nullptr) {}
    ptr_iterator(T* v) : pos_(v) {}
    ~ptr_iterator() {}

    iterator  operator++(int) /* postfix */         { return pos_++; }
    iterator& operator++()    /* prefix */          { ++pos_; return *this; }
    reference operator* () const                    { return *pos_; }
    pointer   operator->() const                    { return pos_; }
    iterator  operator+ (difference_type v)   const { return pos_ + v; }
    bool      operator==(const iterator& rhs) const { return pos_ == rhs.pos_; }
    bool      operator!=(const iterator& rhs) const { return pos_ != rhs.pos_; }
};

template<typename T>
ptr_iterator<T> begin(T *val) { return ptr_iterator<T>(val); }


template<typename T, typename Tsize>
ptr_iterator<T> end(T *val, Tsize size) { return ptr_iterator<T>(val) + size; }

необработанный диапазон указателя на основе цикла обхода. Пожалуйста, поправьте меня, если есть лучший способ сделать цикл на основе диапазона из необработанного указателя.

template<typename T>
class ptr_range
{
    T* begin_;
    T* end_;
public:
    ptr_range(T* ptr, size_t length) : begin_(ptr), end_(ptr + length) { assert(begin_ <= end_); }
    T* begin() const { return begin_; }
    T* end() const { return end_; }
};

template<typename T>
ptr_range<T> range(T* ptr, size_t length) { return ptr_range<T>(ptr, length); }

и простой тест

void DoIteratorTest()
{
    const static size_t size = 10;
    uint8_t *data = new uint8_t[size];
    {
        // Only for iterator test
        uint8_t n = '0';
        auto first = begin(data);
        auto last = end(data, size);
        for (auto it = first; it != last; ++it)
        {
            *it = n++;
        }

        // It's prefer to use the following way:
        for (const auto& n : range(data, size))
        {
            std::cout << " char: " << static_cast<char>(n) << std::endl;
        }
    }
    {
        // Only for iterator test
        ptr_iterator<uint8_t> first(data);
        ptr_iterator<uint8_t> last(first + size);
        std::vector<uint8_t> v1(first, last);

        // It's prefer to use the following way:
        std::vector<uint8_t> v2(data, data + size);
    }
    {
        std::list<std::vector<uint8_t>> queue_;
        queue_.emplace_back(begin(data), end(data, size));
        queue_.emplace_back(data, data + size);
    }
}

прежде всего вы можете посмотреть здесь для списка различных операций необходимо поддерживать отдельные типы итераторов.

далее, когда вы сделали свой класс итератора, вам нужно либо специализироваться std::iterator_traits для него и предоставить некоторые необходимые typedefs (например, категория итератора или тип значения) или альтернативно вывести его из std::iterator, который определяет необходимые typedefs для вас и поэтому может использоваться по умолчанию std::iterator_traits.

отказ от ответственности: я знаю, что некоторые люди не любят cplusplus.com это много, но они предоставляют некоторую действительно полезную информацию об этом.

Я был/нахожусь в одной лодке с вами по разным причинам (частично образовательным, частично ограничениям). Мне пришлось переписать все контейнеры стандартной библиотеки и контейнеры должны соответствовать стандарту. Это означает, что если я поменяю свой контейнер с stl версия, код будет работать одинаково. Это также означало, что мне пришлось переписать итераторы.

во всяком случае, я посмотрел на EASTL. Помимо изучения тонны о контейнерах, которые я никогда узнал все это время с помощью stl контейнеры или через мои курсы. Главная причина в том, что EASTL является более читаемым, чем stl аналог (я нашел это просто из-за отсутствия всех макросов и прямой стиль кодирования). Есть некоторые неприятные вещи там (например, #ifdefs для исключений), но ничего, чтобы сокрушить вас.

Как уже упоминалось, посмотрите на cplusplus.com ссылка на итераторы и контейнеры.

Comments

    Ничего не найдено.