web-dev-qa-db-fra.com

Comment implémenter correctement les itérateurs et les const_iterators personnalisés?

J'ai une classe de conteneur personnalisée pour laquelle j'aimerais écrire les classes iterator et const_iterator.

Je n'avais jamais fait cela auparavant et je n'ai pas réussi à trouver un tutoriel approprié. Quelles sont les directives concernant la création d'itérateurs et que devrais-je connaître?

J'aimerais aussi éviter la duplication de code (je pense que const_iterator et iterator partagent beaucoup de choses; doit-on sous-classer l'autre?).

Note de bas de page: je suis à peu près sûr que Boost a quelque chose à alléger, mais je ne peux pas l'utiliser ici, pour de nombreuses raisons stupides.

211
ereOn
  • Choisissez le type d'itérateur qui convient à votre conteneur: entrée, sortie, transfert, etc.
  • Utilisez les classes d'itérateur de base de la bibliothèque standard. Par exemple, std::iterator avec _random_access_iterator_tag_. Ces classes de base définissent toutes les définitions de type requises par STL et effectuent un autre travail.
  • Pour éviter la duplication de code, la classe itérateur doit être une classe de modèle et être paramétrée par "type de valeur", "type de pointeur", "type de référence" ou tous (dépend de l'implémentation). Par exemple:

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

    Remarquez les définitions de type _iterator_type_ et _const_iterator_type_: ce sont des types pour vos itérateurs non-const et const.

Voir aussi: référence de bibliothèque standard

144
Andrey

Je vais vous montrer comment vous pouvez facilement définir des itérateurs pour vos conteneurs personnalisés, mais juste au cas où j’aurais créé une bibliothèque c ++ 11 vous permettant de créer facilement des itérateurs personnalisés avec un comportement personnalisé pour tout type de conteneur, contigu ou négatif. non constiguë.

Vous pouvez le trouver sur github à l’adresse https://github.com/navyenzo/blIteratorAPI

Voici les étapes simples à suivre pour créer et utiliser des itérateurs personnalisés:

  1. Créez votre classe "itérateur personnalisé".
  2. Définissez les typedefs dans votre classe "conteneur personnalisé".
    • Par exemple: typedef blRawIterator< Type > iterator;
    • Par exemple: typedef blRawIterator< const Type > const_iterator;
  3. Définir les fonctions "begin" "end"
    • Par exemple: iterator begin(){return iterator(&m_data[0]);};
    • Par exemple: const_iterator cbegin()const{return const_iterator(&m_data[0]);};
  4. Avaient fini!!!

Enfin, pour définir nos classes d'itérateurs personnalisées:

REMARQUE:Lors de la définition d'itérateurs personnalisés, nous dérivons des catégories d'itérateurs standard pour permettre aux algorithmes STL de connaître le type d'itérateur que nous avons créé.

Dans cet exemple, je définis un itérateur à accès aléatoire et un itérateur à accès aléatoire inverse:

1.

//-------------------------------------------------------------------
// Raw iterator with random access
//-------------------------------------------------------------------
template<typename blDataType>
class blRawIterator : public std::iterator<std::random_access_iterator_tag,
                                           blDataType,
                                           ptrdiff_t,
                                           blDataType*,
                                           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 ptrdiff_t& movement){m_ptr += movement;return (*this);}
    blRawIterator<blDataType>&                  operator-=(const ptrdiff_t& movement){m_ptr -= movement;return (*this);}
    blRawIterator<blDataType>&                  operator++(){++m_ptr;return (*this);}
    blRawIterator<blDataType>&                  operator--(){--m_ptr;return (*this);}
    blRawIterator<blDataType>                   operator++(ptrdiff_t){auto temp(*this);++m_ptr;return temp;}
    blRawIterator<blDataType>                   operator--(ptrdiff_t){auto temp(*this);--m_ptr;return temp;}
    blRawIterator<blDataType>                   operator+(const ptrdiff_t& movement){auto oldPtr = m_ptr;m_ptr+=movement;auto temp(*this);m_ptr = oldPtr;return temp;}
    blRawIterator<blDataType>                   operator-(const ptrdiff_t& movement){auto oldPtr = m_ptr;m_ptr-=movement;auto temp(*this);m_ptr = oldPtr;return temp;}

    ptrdiff_t                                   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 ptrdiff_t& movement){this->m_ptr -= movement;return (*this);}
    blRawReverseIterator<blDataType>&           operator-=(const ptrdiff_t& 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++(ptrdiff_t){auto temp(*this);--this->m_ptr;return temp;}
    blRawReverseIterator<blDataType>            operator--(ptrdiff_t){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;}

    ptrdiff_t                                   operator-(const blRawReverseIterator<blDataType>& rawReverseIterator){return std::distance(this->getPtr(),rawReverseIterator.getPtr());}

    blRawIterator<blDataType>                   base(){blRawIterator<blDataType> forwardIterator(this->m_ptr); ++forwardIterator; return forwardIterator;}
};
//-------------------------------------------------------------------

Maintenant quelque part dans votre classe de conteneur personnalisée:

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

BONNE CHANCE!!!

48
Enzo

Boost a quelque chose à aider: la bibliothèque Boost.Iterator.

Plus précisément cette page: boost :: iterator_adaptor .

Ce qui est très intéressant, c'est le Exemple de tutoriel qui montre une implémentation complète, à partir de rien, pour un type personnalisé.

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

Comme nous l’avons déjà mentionné, l’essentiel est d’utiliser une implémentation de modèle unique et typedef it.

22
Matthieu M.

Ils oublient souvent que iterator doit être converti en const_iterator mais pas l'inverse. Voici un moyen de le faire:

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

Dans la remarque ci-dessus, comment IntrusiveSlistIterator<T> est converti en IntrusiveSlistIterator<T const>. Si T est déjà const, cette conversion ne sera jamais utilisée.

21
Maxim Egorushkin

Je ne sais pas si Boost a quelque chose qui pourrait aider.

Mon modèle préféré est simple: prenons un argument de modèle égal à value_type, qualifié qualifié ou non. Si nécessaire, également un type de nœud. Alors, tout tombe en place.

Rappelez-vous simplement de paramétrer (template-ize) tout ce qui doit être, y compris le constructeur de copie et operator==. Pour la plupart, la sémantique de const créera un comportement correct.

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;
16
Potatoswatter

Il y a beaucoup de bonnes réponses mais j'ai créé un en-tête de modèle que j'utilise, qui est assez concis et facile à utiliser.

Pour ajouter un itérateur à votre classe, il suffit d’écrire une petite classe pour représenter l’état de l’itérateur avec 7 petites fonctions, dont 2 sont facultatives:

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

Ensuite, vous pouvez l’utiliser comme vous le souhaiteriez avec un itérateur 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
  }
}

J'espère que ça aide.

8
VinGarcia