2009-11-21 2 views
5

OK, donc j'ai deux classes (complètement différentes, projet différent) en utilisant des itérateurs maintenant. On a iterator et reverse_iterator fonctionnant comme prévu, et l'autre, courant a iterator et un const_iterator semi-cassé (spécifiquement, parce que const_iterator dérive de l'itérateur, le code LinkedList<int>::iterator i = const_list.begin() est valide et te permet de modifier la liste const définie ...) .
J'ai l'intention d'ajouter les quatre types à cette classe ... Si je peux.Comment appliquer le principe DRY aux itérateurs en C++? (itérateur, const_iterator, reverse_iterator, const_reverse_iterator)

Comment procéder pour minimiser le code de copie/collage et modifier uniquement le type de retour? Créer une classe de base comme base_iterator à partir de? Créer un iterator ou const_iterator et hériter de cela? Hériter de certains std :: class? Si l'un de ces cas est la "meilleure" approche, quel code va où?
Peut-être qu'aucune des alternatives n'est bonne? Je suis assez perdu ici, et je ne trouve pas beaucoup de matériel de référence.

Tout conseil est apprécié, mais s'il vous plaît gardez à l'esprit que je suis nouveau sur le sujet (les deux itérateurs et C++ en général, en particulier OOP). J'ai essayé, en vain, d'étudier les fichiers d'en-tête livrés avec GCC - ils ne sont pas exactement le tutoriel que je cherche.

+0

implémentations stdlib sont, en général, des choix pauvres à apprendre, pour toutes les langues. Ils doivent souvent traiter avec des interfaces externes (par exemple, le système d'exploitation, d'autres langages), doivent être rétrocompatibles avec le code de la dernière décennie et d'autres facteurs qui ne s'appliquent tout simplement pas à vous. En résumé: ils ne sont pas écrits avec l'enseignement comme objectif. Un bon livre est un must et vous servira beaucoup mieux. –

Répondre

3

Parfois, l'application générale de la règle dite DRY (Don't Repeat Yourself, pour ceux qui ne sont pas familiers) n'est pas la meilleure approche. Surtout si vous êtes nouveau à la langue (C++ et itérateurs) et OOP lui-même (méthodologie), il y a peu d'avantages à essayer de minimiser la quantité de code que vous devez écrire dès maintenant.

Je voudrais implémenter les deux itérateurs en utilisant le code approprié pour chacun d'eux. Peut-être après avoir plus d'expérience avec le langage, les outils et les techniques, puis revenir en arrière et voir si vous pouvez réduire la quantité de code en factorisant le code commun.

+0

Avez-vous dû répéter la définition de DRY autant que vous le faites maintenant par réflexe? : P –

+0

Probablement un bon conseil, et je l'ai pris. J'ai fait deux classes de base séparées, itérateur et const_iterator (les deux sous-classes de std :: itérateur), et basé reverse_iterator sur itérateur, et const_reverse_iterator sur reverse_iterator. Le nombre total de lignes pour les itérateurs est de ~ 70-75. Pas mal pour 4 types différents. Cela exclut la documentation en ligne à venir, mais en incluant suffisamment d'espaces pour la rendre lisible.:) – exscape

+0

Alors que "implémenter d'abord, refactor later" semble être un bon conseil * général *, cette réponse semble être un faux-pas pour implémenter des itérateurs, où il faut savoir quel code est souvent dupliqué. Alors que 'iterator' et' reverse_iterator' pourraient avoir des différences significatives dans l'implémentation, 'iterator' et' const_iterator' * devraient * être essentiellement identiques (au moins pour toute implémentation sensée). – jamesdlin

0

LinkedList<int>::iterator i = const_list.begin() À quoi ressemble votre méthode de début? En étudiant la STL, vous pouvez voir que les conteneurs définissent deux de ces méthodes avec les signatures suivantes:

const_iterator begin() const; 
iterator begin(); 

Vous ne devriez pas avoir de problème pour obtenir un iterator d'un objet qualifié const. Je ne pense pas que DRY s'applique ici.

+0

Il a actuellement (incorrectement) que const_iterator dérive de l'itérateur, donc avec les surcharges const/non-const habituelles pour begin, son code est toujours capable de convertir implicitement n'importe quel const_iterator en un itérateur. –

1

Faire l'itérateur dériver de const_iterator au lieu de l'inverse. Utilisez correctement const_cast (en tant que détail d'implémentation, non exposé aux utilisateurs). Cela fonctionne très bien dans les cas simples et les modèles que "les itérateurs sont const_iterators" directement.

Lorsque ce démarre pour exiger des commentaires de clarification dans votre code, puis écrire des classes distinctes. Vous pouvez utiliser des macros localisées pour générer un code similaire pour vous, pour ne pas répéter la logique:

struct Container { 
#define G(This) \ 
This& operator++() { ++_internal_member; return *this; } \ 
This operator++(int) { This copy (*this); ++*this; return copy; } 

    struct iterator { 
    G(iterator) 
    }; 
    struct const_iterator { 
    G(const_iterator) 
    const_iterator(iterator); // and other const_iterator specific code 
    }; 
#undef G 
}; 

que la macro est scope/localisée est importante, et, bien sûr, ne l'utiliser que si elle vous aide réellement — si il en résulte un code moins lisible pour vous, tapez-le explicitement. Et à propos des itérateurs inversés: vous pouvez utiliser std::reverse_iterator pour envelopper vos itérateurs "normaux" dans de nombreux cas, au lieu de les réécrire.

struct Container { 
    struct iterator {/*...*/}; 
    struct const_iterator {/*...*/}; 

    typedef std::reverse_iterator<iterator> reverse_iterator; 
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator; 
}; 
0

Une fois que j'utilisé l'approche suivante:

  1. faire une classe de modèle common_iterator
  2. ajouter typedefs pour "iterator" et "const_iterator"
  3. Ajouter à "common_iterator" une prise de constructeur " itérateur "type

Pour" itérateur ", le constructeur supplémentaire remplacera la copie par défaut uctor, dans mon cas c'était équivalent au constructeur de copie par défaut.

Pour « const_iterator » ce sera un constructeur supplémentaire qui permet de construire « const_iterator » de « iterator »

3

Il est en fait très simple.

Tout d'abord, jetez un oeil à la bibliothèque Boost.Iterator. Deuxièmement: vous devez déclarer une classe de base (il est bien expliqué dans l'exemple comment procéder) qui sera similaire à celle-ci. Vous implémentez les opérations pour déplacer votre pointeur là-bas. Notez que, comme il s'agit d'une adaptation par rapport à un itérateur déjà existant, vous pouvez l'implémenter avec seulement quelques traits. C'est vraiment impressionnant.

Troisièmement, il vous suffit TypeDef avec le const et les versions non-const:

typedef BaseIterator<Value> iterator; 
typedef BaseIterator<const Value> const_iterator; 

La bibliothèque vous montrer comment faire explicitement la version const_iterator soit de la version constructible iterator.

En quatrième lieu, pour la chose inverse, il y a un objet reverse_iterator spécial, qui est construit sur un iterator régulier et revenir en arrière :)

Dans l'ensemble, d'une manière très élégante et encore pleinement fonctionnel de la définition itérateurs sur classes personnalisées.

J'écris régulièrement mes propres adaptateurs de conteneur, et c'est moins sur le DRY que de me sauver un peu de frappe!

+0

+1 pour boost :: iterator_adaptor. Je l'utilise aussi et ça marche très bien pour moi. – n1ckp

0

Une version plus concrète de what maxim1000 suggested:

#include <type_traits> 

template<typename Container, bool forward> 
class iterator_base 
{ 
public: 
    using value_type = 
     typename std::conditional<std::is_const<Container>::value, 
           const typename Container::value_type, 
           typename Container::value_type>::type; 

    iterator_base() { } 

    // For conversions from iterator to const_iterator. 
    template<typename U> 
    iterator_base(const iterator_base<U, forward>& other) 
    : c(other.c) 
    { 
     // .... 
    } 

    value_type& operator*() const 
    { 
     // ... 
    } 

    iterator_base& operator++() 
    { 
     if (forward) 
     { 
      // ... 
     } 
     else 
     { 
      // ... 
     } 
    } 

    iterator_base& operator++(int) 
    { 
     iterator_base copy(*this); 
     ++*this; 
     return copy; 
    } 

private: 
    Container* c = nullptr; 
    // ... 
}; 

using iterator = iterator_base<self_type, true>; 
using const_iterator = iterator_base<const self_type, true>; 

using reverse_iterator = iterator_base<self_type, false>; 
using const_reverse_iterator = iterator_base<const self_type, false>; 
Questions connexes