2010-06-07 4 views
4

Vous avez une question sur ce qui serait la meilleure façon de mettre en œuvre itérateurs dans les domaines suivants:C++ itérateurs et l'héritage

Dire que j'ai une classe de base basé sur un modèle « Liste » et deux sous-classes « ListImpl1 » et « ListImpl2 ». L'exigence de base de la classe de base est de itérables être-à-dire que je peux faire:

for(List<T>::iterator it = list->begin(); it != list->end(); it++){ 
    ... 
} 

Je veux aussi permettre par exemple d'addition iterator:

for(List<T>::iterator it = list->begin()+5; it != list->end(); it++){ 
    ... 
} 

Le problème est que la mise en œuvre du iterator pour ListImpl1 sera différent de celui de ListImpl2. J'ai contourné ceci en utilisant un wrapper ListIterator contenant un pointeur sur un ListIteratorImpl avec des sous-classes ListIteratorImpl2 et ListIteratorImpl2, mais tout cela devient assez compliqué, particulièrement quand vous devez implémenter operator + dans ListIterator.

Avez-vous des idées sur une meilleure conception pour contourner ces problèmes?

+0

Pourquoi ne pas utiliser ['std :: advance'] (http://www.cplusplus.com/reference/std/iterator/advance/)? – kennytm

+0

Merci, mais disons que j'ai besoin de fournir un opérateur + plus efficace que de simplement utiliser l'opérateur ++ à plusieurs reprises. Vraisemblablement, j'ai toujours le même problème? – user360366

+0

La distinction entre un itérateur avant (ce que 'std :: avance()' est pour) et un itérateur d'accès aléatoire est qu'un itérateur avant n'a pas une implémentation 'opérateur +' efficace (liste liée). Cette question implique un itérateur qui est un accès statiquement aléatoire, mais qui a un comportement dynamique pour cet accès. –

Répondre

5

Si vous pouvez vous contenter de faire List<T>::iterator non virtuel, déléguer ensuite la virtualité off Ajouter à la liste rend les choses simples:

template<typename T> 
class List 
{ 
    virtual void add_assign(iterator& left, int right) = 0; 

public: 
    class iterator 
    { 
     const List* list; 
     const T* item; 
    public: 
     iterator(const List* list, const T* item) : list(list), item(item) {} 

     iterator& operator +=(int right) 
     { 
      list->add_assign(*this, right); 
      return *this; 
     } 
     static iterator operator +(iterator const& left, int right) 
     { 
      iterator result = left; 
      result += right; 
      return result; 
     } 
    }; 

    virtual iterator begin() const = 0; 
    virtual iterator end() const = 0; 
}; 

Dans le cas contraire (si les itérateurs ont besoin de stocker des données sensiblement différentes, par exemple) , alors vous devez faire la régulière, ennuyeux à la mise en œuvre de pointeur pour obtenir votre virtualité:

template<typename T> 
class List 
{ 
    class ItImpl 
    { 
     virtual ItImpl* clone() = 0; 
     virtual void increment() = 0; 
     virtual void add(int right) = 0; 
    }; 
public: 
    class iterator 
    { 
     ItImpl* impl; 
    public: 
     // Boring memory management stuff. 
     iterator() : impl() {} 
     iterator(ItImpl* impl) : impl(impl) {} 
     iterator(iterator const& right) : impl(right.impl->clone()) {} 
     ~iterator() { delete impl; } 
     iterator& operator=(iterator const& right) 
     { 
      delete impl; 
      impl = right.impl->clone(); 
      return *this; 
     } 

     // forward operators to virtual calls through impl. 
     iterator& operator+=(int right) 
     { 
      impl->add(right); 
      return *this; 
     } 
     iterator& operator++() 
     { 
      impl->increment(); 
      return *this; 
     } 
    }; 
}; 

template<typename T> 
static List<T>::iterator operator+(List<T>::iterator const& left, int right) 
{ 
    List<T>::iterator result = left; 
    result += right; 
    return result; 
} 

template<typename T> 
class MagicList : public List<T> 
{ 
    class MagicItImpl : public ItImpl 
    { 
     const MagicList* list; 
     const magic* the_magic; 
     // implement ... 
    }; 
public: 
    iterator begin() const { return iterator(new MagicItImpl(this, begin_magic)); } 
    iterator end() const { return iterator(new MagicItImpl(this, end_magic)); } 
}; 
0

Le problème est que la mise en œuvre du iterator pour ListImpl1 sera différente de celle pour ListImpl2. Je suis autour de ce par en utilisant une enveloppe contenant ListIterator un pointeur vers un ListIteratorImpl avec les sous-classes ListIteratorImpl2 et ListIteratorImpl2, mais il est tout obtenir assez en désordre, surtout quand dont vous avez besoin pour mettre en œuvre l'opérateur + dans le ListIterator.

Cette conception est bien à mon humble avis, je ne vois rien de désordonné à ce sujet. Sauf pour l'égalité et la soustraction, les opérations de l'itérateur peuvent être mis en œuvre par fonction virtuelle assez facilement, de sorte que vous aurez quelque chose comme

class ListIteratorInterface // abstract 
{ 
protected: 
    virtual Data& operator*()=0; 
    // and other operations 
}; 
class ListIteratorA; 
class ListIteratorB; // implementation of the above 
class ListIterator 
{ 
    ListIteratorInterface* impl_; 
public: 
    // when you create this, allocate impl_ on the heap 
    // operations, forward anything to impl_ 
}; 
+0

C'est à peu près ce que j'ai en ce moment. Là où cela devient plus compliqué, c'est l'implémentation de l'opérateur + dans la classe ListIterator. Parce que j'ai besoin de retourner un nouveau ListIterator et ListIteratorA/B, je suppose que je dois créer une méthode clone() virtuelle dans ListIteratorInterface pour créer une nouvelle impl du type correct. – user360366

0

Vous pouvez stocker opérateur + comme méthode privée virtuelle dans la classe de base, et ont la l'itérateur appelle cela. Vous pouvez également envisager des classes de listes polymorphes statiques, plutôt que le polymorphisme d'exécution.

0

dire que j'ai une classe de base basé sur un modèle « Liste » et deux sous-classes « ListImpl1 » et « l istImpl2 "

Que gagnez-vous en utilisant l'héritage ici?

+0

Juste pour le polymorphisme – user360366

+0

@jom: Il existe différents types de polymorphisme. Les conteneurs STL sont basés sur le "polymorphisme temporel de compilation/liaison statique/généricité/modèles", pas le type de polymorphisme que vous semblez avoir en tête. – fredoverflow

+0

Je parle de polymorphisme d'exécution, c'est-à-dire l'utilisation de la liste est une interface avec plusieurs implémentations ListImpl1 et ListImpl2 , transparente pour le code client, bien qu'il y ait évidemment un polymorphisme statique ici aussi. – user360366

0

Il y a quelque chose de très important parmi itérateurs, appelé Iterator Catégorie:

  • InputIterator
  • OutputIterator
  • ForwardIterator
  • BidirectionalIterator
  • RandomAccessIterator

Chaque catégorie def ine un ensemble exact d'opérations supportées efficacement par l'itérateur. Ici, il semble que vous souhaitiez désactiver ce mécanisme d'identification puissant pour créer une sorte de catégorie bâtarde dans laquelle les opérations sont toutes présentes, mais aucune garantie n'est faite sur leur efficacité.

Je pense que votre design sent.

+0

Je ne vois pas vraiment ce que tu veux dire. C'est juste un itérateur d'accès aléatoire garantissant un temps constant. – user360366

+0

Ensuite, 'operator +' devrait faire partie de l'interface (acceptant un entier signé comme argument côté droit). Quel est votre problème? –

+0

Le problème est que l'opérateur + renvoie un itérateur, mais l'implémentation de l'itérateur est différente dans les deux sous-classes, vous devez donc cacher cette implémentation dans un itérateur 'wrapper'. Ma question était de savoir s'il y avait une meilleure façon de procéder. – user360366