Як правильно реалізувати власні ітератори та const_iterators?


240

У мене є спеціальний клас контейнерів, для якого я б хотів написати iteratorта const_iteratorкласи.

Я ніколи цього не робив, і мені не вдалося знайти відповідного способу. Які вказівки щодо створення ітератора, і що я повинен знати?

Я також хотів би уникнути дублювання коду (я відчуваю це const_iteratorі iteratorподіляю багато речей; повинен один підклас інший?).

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



Чи взагалі розглядається шаблон ітератора GoF?
DumbCoder

3
@DumbCoder: У C ++ часто бажано мати ітератори, сумісні зі стандартом STL, оскільки вони добре працюватимуть із усіма існуючими контейнерами та алгоритмами, передбаченими STL. Незважаючи на те, що концепція схожа, існують деякі відмінності у шаблоні, запропонованому Міністерством фінансів.
Бьорн Поллекс

Я опублікував зразок спеціального ітератора тут
Valdemar_Rudolfovich,

1
Складність цих відповідей говорить про те, що C ++ - це або мова, недостойна нічого, крім домашніх завдань для підрослих підрядників, або відповіді є надскладними та неправильними. Має бути простіший шлях у Cpp? Як і CMake та Automake перед цим відносно виготовлення, сировина C, зварена з прототипу пітона, здається набагато простішою, ніж ця.
Крістофер

Відповіді:


157
  • Виберіть тип ітератора, який відповідає вашому контейнеру: введення, виведення, пересилання тощо.
  • Використовуйте базові класи ітераторів зі стандартної бібліотеки. Наприклад, std::iteratorз random_access_iterator_tag. Ці базові класи визначають усі визначення типів, необхідні для STL, та виконують інші роботи.
  • Щоб уникнути дублювання коду, ітератор класу повинен бути шаблоновим класом і параметризуватися "типом значення", "типом вказівника", "типом посилання" або всіма ними (залежить від реалізації). Наприклад:

    // iterator class is parametrized by pointer type
    template <typename PointerType> class MyIterator {
        // iterator class definition goes here
    };
    
    typedef MyIterator<int*> iterator_type;
    typedef MyIterator<const int*> const_iterator_type;

    Повідомлення iterator_typeі const_iterator_typeтипу визначення: вони є типами для вашого неконстантного і константних ітераторів.

Див. Також: стандартна довідка про бібліотеку

EDIT: std::iterator застаріло, оскільки C ++ 17. Дивіться відповідну дискусію тут .


8
@Potatoswatter: Не сприймали цього, але, ей, random_access_iteratorце не в стандарті, і відповідь не обробляє змінну конвертацію const. Ви, мабуть, хочете успадкувати, наприклад, std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>хоча.
Яків Галка

2
Так, я не зовсім впевнений, як це працює. Якщо у мене є метод RefType operator*() { ... }, я на крок ближче - але це не допомагає, тому що мені ще потрібно RefType operator*() const { ... }.
Осінь,



5
Якщо це застаріле, який правильний "новий" спосіб зробити це замість цього?
SasQ

56

Я збираюся показати вам, як ви можете легко визначити ітератори для своїх спеціальних контейнерів, але про всяк випадок, якщо я створив бібліотеку c ++ 11, яка дозволяє вам легко створювати власні ітератори з власною поведінкою для будь-якого типу контейнерів, суміжних або безперервний.

Ви можете знайти його на Github

Ось прості дії зі створення та використання користувальницьких ітераторів:

  1. Створіть свій клас "спеціальний ітератор".
  2. Визначте typedefs у своєму класі "спеціальний контейнер".
    • напр typedef blRawIterator< Type > iterator;
    • напр typedef blRawIterator< const Type > const_iterator;
  3. Визначте функції "почати" та "закінчити"
    • напр iterator begin(){return iterator(&m_data[0]);};
    • напр const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Були зроблені!!!

Нарешті, на визначення наших спеціальних класів ітераторів:

ПРИМІТКА. Під час визначення користувальницьких ітераторів ми виходимо зі стандартних категорій ітераторів, щоб алгоритми STL знали тип створеного нами ітератора.

У цьому прикладі я визначаю ітератор випадкового доступу та ітератор зворотного випадкового доступу:

  1. //-------------------------------------------------------------------
    // Raw iterator with random access
    //-------------------------------------------------------------------
    template<typename blDataType>
    class blRawIterator
    {
    public:
    
        using iterator_category = std::random_access_iterator_tag;
        using value_type = blDataType;
        using difference_type = std::ptrdiff_t;
        using pointer = blDataType*;
        using reference = blDataType&;
    
    public:
    
        blRawIterator(blDataType* ptr = nullptr){m_ptr = ptr;}
        blRawIterator(const blRawIterator<blDataType>& rawIterator) = default;
        ~blRawIterator(){}
    
        blRawIterator<blDataType>&                  operator=(const blRawIterator<blDataType>& rawIterator) = default;
        blRawIterator<blDataType>&                  operator=(blDataType* ptr){m_ptr = ptr;return (*this);}
    
        operator                                    bool()const
        {
            if(m_ptr)
                return true;
            else
                return false;
        }
    
        bool                                        operator==(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr == rawIterator.getConstPtr());}
        bool                                        operator!=(const blRawIterator<blDataType>& rawIterator)const{return (m_ptr != rawIterator.getConstPtr());}
    
        blRawIterator<blDataType>&                  operator+=(const difference_type& movement){m_ptr += movement;return (*this);}
        blRawIterator<blDataType>&                  operator-=(const difference_type& movement){m_ptr -= movement;return (*this);}
        blRawIterator<blDataType>&                  operator++(){++m_ptr;return (*this);}
        blRawIterator<blDataType>&                  operator--(){--m_ptr;return (*this);}
        blRawIterator<blDataType>                   operator++(int){auto temp(*this);++m_ptr;return temp;}
        blRawIterator<blDataType>                   operator--(int){auto temp(*this);--m_ptr;return temp;}
        blRawIterator<blDataType>                   operator+(const difference_type& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
        blRawIterator<blDataType>                   operator-(const difference_type& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
    
        difference_type                             operator-(const blRawIterator<blDataType>& rawIterator){return std::distance(rawIterator.getPtr(),this->getPtr());}
    
        blDataType&                                 operator*(){return *m_ptr;}
        const blDataType&                           operator*()const{return *m_ptr;}
        blDataType*                                 operator->(){return m_ptr;}
    
        blDataType*                                 getPtr()const{return m_ptr;}
        const blDataType*                           getConstPtr()const{return m_ptr;}
    
    protected:
    
        blDataType*                                 m_ptr;
    };
    //-------------------------------------------------------------------
  2. //-------------------------------------------------------------------
    // Raw reverse iterator with random access
    //-------------------------------------------------------------------
    template<typename blDataType>
    class blRawReverseIterator : public blRawIterator<blDataType>
    {
    public:
    
        blRawReverseIterator(blDataType* ptr = nullptr):blRawIterator<blDataType>(ptr){}
        blRawReverseIterator(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();}
        blRawReverseIterator(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
        ~blRawReverseIterator(){}
    
        blRawReverseIterator<blDataType>&           operator=(const blRawReverseIterator<blDataType>& rawReverseIterator) = default;
        blRawReverseIterator<blDataType>&           operator=(const blRawIterator<blDataType>& rawIterator){this->m_ptr = rawIterator.getPtr();return (*this);}
        blRawReverseIterator<blDataType>&           operator=(blDataType* ptr){this->setPtr(ptr);return (*this);}
    
        blRawReverseIterator<blDataType>&           operator+=(const difference_type& movement){this->m_ptr -= movement;return (*this);}
        blRawReverseIterator<blDataType>&           operator-=(const difference_type& movement){this->m_ptr += movement;return (*this);}
        blRawReverseIterator<blDataType>&           operator++(){--this->m_ptr;return (*this);}
        blRawReverseIterator<blDataType>&           operator--(){++this->m_ptr;return (*this);}
        blRawReverseIterator<blDataType>            operator++(int){auto temp(*this);--this->m_ptr;return temp;}
        blRawReverseIterator<blDataType>            operator--(int){auto temp(*this);++this->m_ptr;return temp;}
        blRawReverseIterator<blDataType>            operator+(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr-=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
        blRawReverseIterator<blDataType>            operator-(const int& movement){auto oldPtr = this->m_ptr;this->m_ptr+=movement;auto temp(*this);this->m_ptr = oldPtr;return temp;}
    
        difference_type                             operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}
    
        blRawIterator<blDataType>                   base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
    };
    //-------------------------------------------------------------------

Тепер десь у вашому користувальницькому класі контейнерів:

template<typename blDataType>
class blCustomContainer
{
public: // The typedefs

    typedef blRawIterator<blDataType>              iterator;
    typedef blRawIterator<const blDataType>        const_iterator;

    typedef blRawReverseIterator<blDataType>       reverse_iterator;
    typedef blRawReverseIterator<const blDataType> const_reverse_iterator;

                            .
                            .
                            .

public:  // The begin/end functions

    iterator                                       begin(){return iterator(&m_data[0]);}
    iterator                                       end(){return iterator(&m_data[m_size]);}

    const_iterator                                 cbegin(){return const_iterator(&m_data[0]);}
    const_iterator                                 cend(){return const_iterator(&m_data[m_size]);}

    reverse_iterator                               rbegin(){return reverse_iterator(&m_data[m_size - 1]);}
    reverse_iterator                               rend(){return reverse_iterator(&m_data[-1]);}

    const_reverse_iterator                         crbegin(){return const_reverse_iterator(&m_data[m_size - 1]);}
    const_reverse_iterator                         crend(){return const_reverse_iterator(&m_data[-1]);}

                            .
                            .
                            .
    // This is the pointer to the
    // beginning of the data
    // This allows the container
    // to either "view" data owned
    // by other containers or to
    // own its own data
    // You would implement a "create"
    // method for owning the data
    // and a "wrap" method for viewing
    // data owned by other containers

    blDataType*                                    m_data;
};

Я думаю, що оператор + і оператор - можуть виконати операції назад. Схоже, що оператор + віднімає рух від покажчика не додає, а оператор додає його. Це здається на зворотному
боці

Це для зворотного ітератора, оператор + повинен рухатись назад, а оператор - рухатися вперед
Enzo

2
Дивовижно. Прийнята відповідь - занадто високий рівень. Це круто. Дякую, Енцо.
ФернандоЗ

Вам потрібно відредагувати свою відповідь. Якщо припустимо, що m_data було розподілено елементами m_size, ви отримуєте Undefined Behavior: m_data[m_size]є UB. Ви можете просто виправити його, замінивши на m_data+m_size. Для зворотних ітераторів і те, m_data[-1]і інше, m_data-1є неправильним (UB). Для виправлення reverse_iterators вам потрібно буде використовувати "покажчики на трюк наступного елемента".
Арно

Арно, я щойно додав член вказівника до спеціального класу контейнерів, що краще показує, що я мав на увазі.
Енцо

24

Вони часто забувають, що iteratorповинні перейти, const_iteratorале не навпаки. Ось такий спосіб зробити:

template<class T, class Tag = void>
class IntrusiveSlistIterator
   : public std::iterator<std::forward_iterator_tag, T>
{
    typedef SlistNode<Tag> Node;
    Node* node_;

public:
    IntrusiveSlistIterator(Node* node);

    T& operator*() const;
    T* operator->() const;

    IntrusiveSlistIterator& operator++();
    IntrusiveSlistIterator operator++(int);

    friend bool operator==(IntrusiveSlistIterator a, IntrusiveSlistIterator b);
    friend bool operator!=(IntrusiveSlistIterator a, IntrusiveSlistIterator b);

    // one way conversion: iterator -> const_iterator
    operator IntrusiveSlistIterator<T const, Tag>() const;
};

У зазначеному вище повідомленні про те, як IntrusiveSlistIterator<T>перетворюється IntrusiveSlistIterator<T const>. Якщо Tвже, constця конверсія ніколи не використовується.


Насправді, ви також можете це зробити навпаки, визначивши конструктор копій, який є шаблоном, він не буде компілюватися, якщо ви спробуєте передати базовий тип з constне- const.
Матьє М.

Чи не закінчитеся з інвалідом IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const?
Potatoswatter

Так, це дійсно, але Комо дає попередження, і я підозрюю, що ще багато інших будуть. Це enable_ifможе виправити, але…
Potatoswatter

Я не турбувався з enable_if, тому що компілятор це все одно відключає, хоча деякі компілятори дають попередження (g ++ бути гарним хлопчиком не попереджає).
Максим Єгорушкін

1
@Matthieu: Якщо йдеться з конструктором шаблонів, при перетворенні const_iterator в ітератор компілятор видає помилку всередині конструктора, змушуючи користувача почухати голову в замішанні і вимовити wtf. З оператором перетворення, який я розмістив, компілятор просто каже, що немає відповідного перетворення з const_iterator в ітератор, що, IMO, більш зрозуміло.
Максим Єгорушкін

23

Boost може щось допомогти: бібліотека Boost.Iterator.

Точніше ця сторінка: boost :: iterator_adaptor .

Що дуже цікаво, це приклад підручника, який показує повну реалізацію для власного типу.

template <class Value>
class node_iter
  : public boost::iterator_adaptor<
        node_iter<Value>                // Derived
      , Value*                          // Base
      , boost::use_default              // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{
 private:
    struct enabler {};  // a private type avoids misuse

 public:
    node_iter()
      : node_iter::iterator_adaptor_(0) {}

    explicit node_iter(Value* p)
      : node_iter::iterator_adaptor_(p) {}

    // iterator convertible to const_iterator, not vice-versa
    template <class OtherValue>
    node_iter(
        node_iter<OtherValue> const& other
      , typename boost::enable_if<
            boost::is_convertible<OtherValue*,Value*>
          , enabler
        >::type = enabler()
    )
      : node_iter::iterator_adaptor_(other.base()) {}

 private:
    friend class boost::iterator_core_access;
    void increment() { this->base_reference() = this->base()->next(); }
};

Як вже було сказано, головний момент - використовувати єдину реалізацію шаблону і typedefце.


Чи можете ви пояснити значення цього коментаря? // a private type avoids misuse
kevinarpe

@kevinarpe: enablerніколи не має наміру надавати абонента, тому я гадаю, що вони роблять це приватним, щоб уникнути випадкових спроб людей передати його. Я не думаю, що, з іншого боку, це може створити будь-яку проблему, щоб насправді її пройти, оскільки захист є enable_if.
Матьє М.

16

Я не знаю, чи є у Boost щось, що допомогло б.

Моя краща модель проста: візьміть аргумент шаблону, який дорівнює value_type, або const кваліфікований, або ні. При необхідності також тип вузла. Тоді, ну, все на зразок стає.

Просто не забудьте параметризувати (template-ize) все, що потрібно, включаючи конструктор копій та operator==. Здебільшого семантика constстворення створить правильну поведінку.

template< class ValueType, class NodeType >
struct my_iterator
 : std::iterator< std::bidirectional_iterator_tag, T > {
    ValueType &operator*() { return cur->payload; }

    template< class VT2, class NT2 >
    friend bool operator==
        ( my_iterator const &lhs, my_iterator< VT2, NT2 > const &rhs );

    // etc.

private:
    NodeType *cur;

    friend class my_container;
    my_iterator( NodeType * ); // private constructor for begin, end
};

typedef my_iterator< T, my_node< T > > iterator;
typedef my_iterator< T const, my_node< T > const > const_iterator;

Примітка. Схоже, ваш ітератор конверсій-> const_iterator і назад порушені.
Максим Єгорушкін

@Maxim: Так, я фактично не можу знайти жодного прикладу використання моєї техніки: vP. Я не впевнений, що ви маєте на увазі, що конверсії порушені, оскільки я просто не проілюстрував їх, але може виникнути проблема з доступом curвід ітератора протилежної швидкості. Рішення, яке спадає на думку, є friend my_container::const_iterator; friend my_container::iterator;, але я не думаю, що так я робив раніше ... все одно цей загальний контур працює.
Potatoswatter

1
* зробити це friend classв обох випадках.
Potatoswatter

Минув деякий час, але зараз я пригадую, що перетворення повинні бути спроектовані (SFINAE) на чітко сформованій ініціалізації базового члена. Це слід за схемою SCARY (але ця публікація передує цій термінології).
Potatoswatter

13

Хороших відповідей є багато, але я створив заголовок шаблону, який я використовую, досить стислий і простий у використанні.

Щоб додати ітератор до свого класу, необхідно лише написати невеликий клас, який представляє стан ітератора з 7 малих функцій, з яких 2 необов’язкові:

#include <iostream>
#include <vector>
#include "iterator_tpl.h"

struct myClass {
  std::vector<float> vec;

  // Add some sane typedefs for STL compliance:
  STL_TYPEDEFS(float);

  struct it_state {
    int pos;
    inline void begin(const myClass* ref) { pos = 0; }
    inline void next(const myClass* ref) { ++pos; }
    inline void end(const myClass* ref) { pos = ref->vec.size(); }
    inline float& get(myClass* ref) { return ref->vec[pos]; }
    inline bool cmp(const it_state& s) const { return pos != s.pos; }

    // Optional to allow operator--() and reverse iterators:
    inline void prev(const myClass* ref) { --pos; }
    // Optional to allow `const_iterator`:
    inline const float& get(const myClass* ref) const { return ref->vec[pos]; }
  };
  // Declare typedef ... iterator;, begin() and end() functions:
  SETUP_ITERATORS(myClass, float&, it_state);
  // Declare typedef ... reverse_iterator;, rbegin() and rend() functions:
  SETUP_REVERSE_ITERATORS(myClass, float&, it_state);
};

Тоді ви можете використовувати його так, як ви очікували від ітератора STL:

int main() {
  myClass c1;
  c1.vec.push_back(1.0);
  c1.vec.push_back(2.0);
  c1.vec.push_back(3.0);

  std::cout << "iterator:" << std::endl;
  for (float& val : c1) {
    std::cout << val << " "; // 1.0 2.0 3.0
  }

  std::cout << "reverse iterator:" << std::endl;
  for (auto it = c1.rbegin(); it != c1.rend(); ++it) {
    std::cout << *it << " "; // 3.0 2.0 1.0
  }
}

Я сподіваюся, що це допомагає.


1
Цей файл шаблону вирішив усі мої проблеми з ітератором!
Perrykipkerrie
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.