Как реализовать итератор в стиле STL и избежать распространенных ошибок?
Я сделал коллекцию, для которой я хочу предоставить итератор с произвольным доступом в стиле STL. Я искал вокруг пример реализации итератора, но я не нашел ни одного. Я знаю о необходимости постоянных перегрузок [] и * операторы. Каковы требования к итератору, чтобы быть "STL-стиль" и каковы некоторые другие подводные камни, чтобы избежать (если таковые имеются)?
дополнительный контекст: это для библиотеки, и я не хочу вводить какую-либо зависимость от нее, если я действительно нужно. Я пишу свою собственную коллекцию, чтобы иметь возможность обеспечить двоичную совместимость между C++03 и C++11 с тем же компилятором (так что нет STL, который, вероятно, сломается).
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за исключением того, что он должен быть неявно конструктивным из Aiteratorи пользователи должны быть в состоянии изменить данные. Обычно его внутренний указатель является указателем на непостоянные данные и имеет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