Come implementare correttamente iteratori e const_iterators personalizzati?


240

Ho una classe contenitore personalizzata per la quale mi piacerebbe scrivere le classi iteratore const_iterator.

Non l'ho mai fatto prima e non sono riuscito a trovare una procedura adeguata. Quali sono le linee guida relative alla creazione dell'iteratore e di cosa dovrei essere a conoscenza?

Vorrei anche evitare la duplicazione del codice (lo sento const_iteratore iteratorcondivido molte cose; una sottoclasse l'altra?).

Nota a piè di pagina: sono abbastanza sicuro che Boost abbia qualcosa per facilitare questo, ma non posso usarlo qui, per molte stupide ragioni.



Il modello Iterator GoF è stato preso in considerazione?
DumbCoder il

3
@DumbCoder: in C ++ è spesso desiderabile avere iteratori conformi a STL, perché funzioneranno bene con tutti i contenitori e gli algoritmi esistenti forniti da STL. Sebbene il concetto sia simile, ci sono alcune differenze rispetto al modello proposto dal GoF.
Björn Pollex,

Ho pubblicato un esempio di iteratore personalizzato qui
Valdemar_Rudolfovich,

1
La complessità di queste risposte suggerisce che il C ++ è o un linguaggio indegno di qualcosa di diverso dai compiti a casa per gli studenti in difficoltà, oppure le risposte sono troppo complicate e sbagliate. Ci deve essere un modo più semplice in Cpp? Come CMake e Automake prima che fosse relativo, la C grezza bollita da un prototipo di Python sembra molto più semplice di così.
Christopher

Risposte:


157
  • Scegli il tipo di iteratore adatto al tuo contenitore: input, output, forward ecc.
  • Utilizzare le classi di iteratori di base dalla libreria standard. Ad esempio, std::iteratorcon random_access_iterator_tag. Queste classi base definiscono tutte le definizioni dei tipi richieste da STL e svolgono altre attività.
  • Per evitare la duplicazione del codice, la classe iteratore dovrebbe essere una classe modello ed essere parametrizzata da "tipo di valore", "tipo di puntatore", "tipo di riferimento" o tutti (dipende dall'implementazione). Per esempio:

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

    Notare iterator_typee const_iterator_typedefinire le definizioni: sono tipi per i tuoi non costanti e costanti.

Vedi anche: riferimento alla libreria standard

EDIT: std::iterator è obsoleto dal C ++ 17. Vedi una discussione relativa qui .


8
@Potatoswatter: non ho effettuato il downgrade di questo, ma, ehi, random_access_iteratornon è nello standard e la risposta non gestisce la conversione da mutabile a const. Probabilmente vuoi ereditare da, ad esempio, std::iterator<random_access_iterator_tag, value_type, ... optional arguments ...>però.
Yakov Galka,

2
Sì, non sono abbastanza sicuro di come funzioni. Se ho il metodo RefType operator*() { ... }, sono un passo avanti - ma non aiuta, perché ho ancora bisogno RefType operator*() const { ... }.
Autumnsault,


20
std::iterator è stato deprecato
diapir

5
Se questo è deprecato, qual è invece il modo "nuovo" di farlo?
SasQ,

56

Ti mostrerò come puoi definire facilmente iteratori per i tuoi contenitori personalizzati, ma nel caso in cui abbia creato una libreria c ++ 11 che ti consenta di creare facilmente iteratori personalizzati con comportamento personalizzato per qualsiasi tipo di contenitore, contiguo o non contigue.

Lo puoi trovare su Github

Ecco i semplici passaggi per creare e utilizzare iteratori personalizzati:

  1. Crea la tua classe "iteratore personalizzato".
  2. Definisci i typedef nella tua classe "contenitore personalizzato".
    • per esempio typedef blRawIterator< Type > iterator;
    • per esempio typedef blRawIterator< const Type > const_iterator;
  3. Definire le funzioni "inizio" e "fine"
    • per esempio iterator begin(){return iterator(&m_data[0]);};
    • per esempio const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Sono stati fatti!!!

Infine, per definire le nostre classi di iteratore personalizzate:

NOTA: quando si definiscono iteratori personalizzati, deriviamo dalle categorie di iteratori standard per comunicare agli algoritmi STL il tipo di iteratore che abbiamo creato.

In questo esempio, definisco un iteratore ad accesso casuale e un iteratore ad accesso casuale inverso:

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

Ora da qualche parte nella tua classe di container personalizzata:

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

Penso che l'operatore + e l'operatore possano avere le operazioni al contrario. Sembra che l'operatore + stia sottraendo il movimento dal puntatore che non sta aggiungendo e l'operatore lo sta aggiungendo. Questo sembra al contrario
Beached il

È per l'iteratore inverso, l'operatore + dovrebbe andare indietro e l'operatore- dovrebbe andare avanti
Enzo,

2
Eccezionale. La risposta accettata è di livello troppo alto. Questo e spettacolare. Grazie Enzo.
FernandoZ,

Devi modificare la tua risposta. Supponendo che m_data sia stato allocato con elementi m_size ottieni Comportamento indefinito: m_data[m_size]è UB. Puoi semplicemente ripararlo sostituendolo con m_data+m_size. Per gli iteratori inversi, entrambi m_data[-1]e m_data-1sono errati (UB). Per correggere reverse_iterators dovrai usare i "puntatori al prossimo trucco elemento".
Arnaud,

Arnaud, ho appena aggiunto il membro puntatore alla classe contenitore personalizzata che mostra meglio cosa intendevo dire.
Enzo,

24

Spesso dimenticano che iteratordevono convertirsi const_iteratorma non viceversa. Ecco un modo per farlo:

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

Nell'avviso precedente come si IntrusiveSlistIterator<T>converte IntrusiveSlistIterator<T const>. Se Tè già constquesta conversione non viene mai utilizzata.


In realtà, puoi anche farlo al contrario definendo un costruttore di copie che è un modello, che non verrà compilato se provi a trasmettere il tipo sottostante da consta non- const.
Matthieu M.,

Non finirai con un invalido IntrusiveSlistIterator<T const, void>::operator IntrusiveSlistIterator<T const, void>() const?
Potatoswatter,

Ah, è valido, ma Comeau dà un avvertimento e sospetto che anche molti altri lo faranno. Una enable_ifpotrebbe risolvere il problema, ma ...
Potatoswatter

Non mi sono preoccupato di enable_if perché il compilatore lo disabilita comunque, anche se alcuni compilatori danno un avvertimento (g ++ essere un bravo ragazzo non avvisa).
Maxim Egorushkin,

1
@Matthieu: se uno va con un costruttore di template, durante la conversione di const_iterator in iteratore il compilatore produce un errore all'interno del costruttore, facendo sì che l'utente si gratta la testa in confusione e completamente wtf. Con l'operatore di conversione che ho pubblicato, il compilatore dice semplicemente che non esiste una conversione adatta da const_iterator a iteratore, che, IMO, è più chiara.
Maxim Egorushkin,

23

Boost ha qualcosa da aiutare: la libreria Boost.Iterator.

Più precisamente questa pagina: boost :: iterator_adaptor .

Ciò che è molto interessante è l' esempio Tutorial che mostra un'implementazione completa, da zero, per un tipo personalizzato.

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

Il punto principale, come è già stato citato, è utilizzare una singola implementazione del modello e typedefesso.


Puoi spiegare il significato di questo commento? // a private type avoids misuse
Kevinevpe

@kevinarpe: enableril chiamante non intende mai fornire il provider, quindi suppongo che lo rendano privato per evitare che le persone provino accidentalmente a passarlo. Non credo, a portata di mano, che potrebbe creare qualche problema per passarlo effettivamente, dal momento che la protezione risiede enable_if.
Matthieu M.

16

Non so se Boost abbia qualcosa che possa aiutare.

Il mio modello preferito è semplice: accetta un argomento template uguale a value_type, qualificato const o no. Se necessario, anche un tipo di nodo. Quindi, beh, tutto va bene.

Ricorda solo di parametrizzare (template-ize) tutto ciò che deve essere, incluso il costruttore di copie e operator== . Per la maggior parte, la semantica di constcreerà un comportamento corretto.

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;

Nota: sembra che le tue conversioni iteratore-> const_iterator e ritorno siano interrotte.
Maxim Egorushkin,

@Maxim: Sì, in realtà non riesco a trovare alcun esempio di utilizzo della mia tecnica: vP. Non sono sicuro di cosa significhi che le conversioni sono interrotte, dal momento che semplicemente non le ho illustrate, ma potrebbe esserci un problema di accesso curdall'iteratore di costanza opposta. La soluzione che mi viene in mente è friend my_container::const_iterator; friend my_container::iterator;, ma non penso che sia stato così prima ... comunque funziona questo schema generale.
Potatoswatter,

1
* fallo friend classin entrambi i casi.
Potatoswatter,

È passato del tempo, ma ora ricordo che le conversioni dovrebbero essere predicate (da SFINAE) sulla buona formazione delle inizializzazioni dei membri sottostanti. Questo segue il modello SCARY (ma questo post precede quella terminologia).
Potatoswatter,

13

Ci sono molte buone risposte ma ho creato un'intestazione del modello che uso che è abbastanza concisa e facile da usare.

Per aggiungere un iteratore alla tua classe è sufficiente scrivere una piccola classe per rappresentare lo stato dell'iteratore con 7 piccole funzioni, di cui 2 opzionali:

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

Quindi puoi usarlo come ti aspetteresti da un iteratore 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
  }
}

Spero possa essere d'aiuto.


1
Questo file modello ha risolto tutti i miei problemi di iteratore!
Perrykipkerrie,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.