361 votes

Comment implémenter un itérateur STL-style et éviter les pièges communs ?

J'ai fait une collection pour laquelle je tiens à fournir une STL-style, random-access itérateur. J'étais à la recherche d'un exemple de mise en œuvre d'un itérateur, mais je n'ai pas trouvé. Je sais que sur la nécessité de const surcharges de [] et * opérateurs. Quelles sont les exigences pour un itérateur pour être "STL-style" et quels sont les pièges à éviter (le cas échéant)?

Plus de contexte: C'est une bibliothèque, et je ne veux pas introduire des dépendances, à moins que j'ai vraiment besoin de. J'écris ma propre collection pour être en mesure de fournir une compatibilité binaire entre C++03 et C++11 avec le même compilateur (donc pas de STL qui serait probablement une pause).

266voto

Mooing Duck Points 27497

http://www.cplusplus.com/reference/std/iterator/ a un tableau qui détaille les spécifications du § 24.2.2 du C++11. Fondamentalement, les itérateurs ont des étiquettes qui décrivent les opérations valides, et les balises ont une heiarchy. Ci-dessous est purement symbolique, ces classes n'existe pas réellement en tant que tel.

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

Vous pouvez soit se spécialiser std::iterator_traits<youriterator>, ou de mettre la même typedefs dans l'itérateur lui-même, ou d'hériter d' std::iterator (qui a ces typedefs). Je préfère la deuxième option, pour éviter de changer les choses dans l' std d'espace de noms, et pour des raisons de lisibilité, mais la plupart des gens héritent de std::iterator.

struct std::iterator_traits<youriterator> {        
    typedef ???? difference_type; //almost always ptrdif_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
};

Remarque le iterator_category devrait être l'une des std::input_iterator_tag, std::output_iterator_tag, std::forward_iterator_tag, std::bidirectional_iterator_tagou std::random_access_iterator_tag, selon les catégories de votre itérateur a été en mesure de répondre à toutes les exigences pour comme détaillé ci-dessus. En fonction de votre itérateur, vous pouvez choisir de se spécialiser std::next, std::prev, std::advance, et std::distance , mais cela est assez rare. Dans de très rares cas, vous pouvez qui pour se spécialiser std::begin et std::end.

Votre conteneur doit probablement aussi un const_iterator, ce qui est une (peut-être mutable) itérateur à données constantes qui est similaire à votre iterator sauf qu'il doit être implicitement constructable à partir d'un iterator , et les utilisateurs doivent être impossible de modifier les données. Il est commun pour les internes pointeur vers un pointeur non-constante des données, et ont iterator hériter d' const_iterator afin de minimiser la duplication de code.

Mon post à la Rédaction de votre propre Conteneur STL a une plus complète conteneur/itérateur prototype.

16voto

Michael Kristofik Points 16035

Le iterator_facade documentation de Boost.Itérateur fournit ce qui ressemble à un tutorial sur la mise en œuvre des itérateurs pour une liste liée. Pourriez-vous l'utiliser comme un point de départ pour la construction d'un accès aléatoire itérateur sur votre conteneur?

Si rien d'autre, vous pouvez prendre un coup d'oeil à l'fonctions de membre et de typedefs fournis par iterator_facade et l'utiliser comme un point de départ pour la construction de votre propre.

10voto

Gnawme Points 1377

Thomas Becker a écrit un article utile sur le sujet ici.

Il y avait aussi cette approche (peut-être plus simple) qui figurait auparavant sur SO : comment implémenter correctement les itérateurs personnalisés et const_iterators ?

5voto

Christian Rau Points 29137

Tout d'abord, vous pouvez regarder ici pour une liste des différentes opérations de l'individu itérateur types de besoin à l'appui.

Ensuite, lorsque vous avez fait votre classe iterator vous devez vous spécialiser std::iterator_traits et fournir certaines nécessaire typedefs (comme itérateur catégorie ou type de valeur) ou bien de le calculer à partir du std::iterator, ce qui définit le besoin typedefs pour vous, et peut donc être utilisé avec la valeur par défaut std::iterator_traits.

avertissement: je sais que certaines personnes n'aiment pas cplusplus.com , mais elles fournissent des informations vraiment utiles.

1voto

Andy T Points 8016

utiliser la bibliothèque de Boost.Iterator

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X