Как реализовать итератор в стиле STL и избежать распространенных ошибок?
Я сделал коллекцию, для которой я хочу предоставить итератор случайного доступа в стиле STL. Я искал пример реализации итератора, но не нашел ни одного. Я знаю о необходимости const перегрузок []
и *
операторы. Каковы требования к итератору, чтобы быть "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
только это должно быть имплицитно технологичная из iterator
и пользователи не могут изменять данные. Обычно его внутренний указатель является указателем на непостоянные данные и имеет iterator
наследовать от const_iterator
чтобы минимизировать код дублирование.
мой пост написание собственного контейнера STL имеет более полный прототип контейнера/итератора.
на документация iterator_facade от Boost.Итератор обеспечивает то, что выглядит как хороший учебник по реализации итераторов для связанного списка. Можете ли вы использовать это в качестве отправной точки для создания итератора произвольного доступа над вашим контейнером?
Если ничего больше, вы можете взглянуть на функции-члены и typedefs, предоставляемые iterator_facade
и использовать его в качестве отправной точки для создания собственного.
Томас Беккер написал полезную статью на эту тему здесь.
был также Этот (возможно, более простой) подход, который появился ранее на SO:как правильно реализовать пользовательские итераторы и const_iterators?
вот пример итератора необработанного указателя.
вы не должны использовать класс iterator для работы с необработанными указателями!
#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 ' S ссылка на итераторы и контейнеры.