web-dev-qa-db-fra.com

Itérateur personnalisé en C++

J'ai une classe TContainer qui est un agrégat de plusieurs pointeurs de collections stl vers la classe TItems.

J'ai besoin de créer un itérateur pour parcourir les éléments de toutes les collections de ma classe TContainer, en résumant le client du fonctionnement interne.

Quel serait un bon moyen de faire cela? Devrais-je créer une classe qui étend un itérateur (si oui, quelle classe d'itérateurs devrais-je étendre), devrais-je créer une classe d'itérateurs qui est un agrégat d'itérateurs?

Je n'ai besoin que d'un itérateur FORWARD_ONLY.

I.E, s'il s'agit de mon conteneur: 

typedef std::vector <TItem*> ItemVector;
class TContainer {
   std::vector <ItemVector *> m_Items;
};

Quel serait un bon itérateur pour parcourir tous les éléments contenus dans les vecteurs de la variable membre m_Items.

28
Sugar

Quand j'ai créé mon propre itérateur (il y a quelque temps maintenant), j'ai hérité de std :: iterator et spécifié le type en tant que premier paramètre de modèle. J'espère que cela pourra aider.

Pour les itérateurs, utilisez forward_iterator_tag plutôt que input_iterator_tag dans le code suivant.

Cette classe a été prise à l'origine dans la classe istream_iterator (et modifiée pour mon propre usage afin de ne pas ressembler davantage à l'istram_iterator).

template<typename T>
class <PLOP>_iterator
         :public std::iterator<std::input_iterator_tag,       // type of iterator
                               T,ptrdiff_t,const T*,const T&> // Info about iterator
{
    public:
        const T& operator*() const;
        const T* operator->() const;
        <PLOP>__iterator& operator++();
        <PLOP>__iterator operator++(int);
        bool equal(<PLOP>__iterator const& rhs) const;
};

template<typename T>
inline bool operator==(<PLOP>__iterator<T> const& lhs,<PLOP>__iterator<T> const& rhs)
{
    return lhs.equal(rhs);
}

Consultez cette documentation sur les tags d'itérateur:
http://www.sgi.com/tech/stl/iterator_tags.html

Après avoir relu les informations sur les itérateurs:
http://www.sgi.com/tech/stl/iterator_traits.html

C’est l’ancienne façon de faire (iterator_tags). L’approche la plus moderne consiste à configurer iterator_traits <> pour que votre itérateur le rende totalement compatible avec la STL.

31
Martin York

Si vous avez accès à Boost, utiliser iterator_facade est la solution la plus robuste et la plus simple à utiliser.

22
James Hopkin

D'abord généralisons un peu:

typedef int value_type;
typedef std::vector<value_type*> inner_range;
typedef std::vector<inner_range*> outer_range;

Maintenant l'itérateur:

struct my_iterator : std::iterator_traits<inner_range::iterator> 
{
    typedef std::forward_iterator_tag iterator_category;

    my_iterator(outer_range::iterator const & outer_iterator, 
                outer_range::iterator const & outer_end)
    : outer_iterator(outer_iterator), outer_end(outer_end)
    { 
        update();
    }

    my_iterator & operator++()
    {
        ++inner_iterator;
        if(inner_iterator == inner_end)
        {
            ++outer_iterator;
            update();
        }
        return *this;
    }

    reference operator*() const
    {   
        return *inner_iterator;
    }

    bool operator==(my_iterator const & rhs) const
    {   
        bool lhs_end = outer_iterator == outer_end;
        bool rhs_end = rhs.outer_iterator == rhs.outer_end;
        if(lhs_end && rhs_end)
            return true;
        if(lhs_end != rhs_end)
            return false;
        return outer_iterator == rhs.outer_iterator 
            && inner_iterator == rhs.inner_iterator;
    }

private:

    outer_range::iterator outer_iterator, outer_end;
    inner_range::iterator inner_iterator, inner_end;

    void update()
    {
        while(outer_iterator != outer_end)
        {
            inner_iterator = (*outer_iterator)->begin();
            inner_end = (*outer_iterator)->end();
            if(inner_iterator == inner_end)
                ++outer_iterator;
            else
                break;
        }
    }    
};

Cette classe suppose que les itérateurs externes contiennent des pointeurs sur les plages internes, ce qui était une exigence de votre question. Cela se reflète dans le membre update, dans les flèches précédant begin() et end(). Vous pouvez remplacer ces flèches par des points si vous souhaitez utiliser cette classe dans la situation plus courante où l'itérateur externe contient les plages internes par valeur. Notez d'ailleurs que cette classe est agnostique au fait que la plage interne contient des pointeurs, seuls les clients de la classe auront besoin de le savoir.

Le code pourrait être plus court si nous utilisons boost::iterator_facade, mais il n'est pas nécessaire d'ajouter une dépendance accrue pour quelque chose d'aussi simple. De plus, les seules parties délicates sont les opérations d'égalité et d'incrément, et nous devons les coder de toute façon.

J'ai laissé les membres suivants de la plaque de la chaudière comme "exercices pour le lecteur":

  • postfix incrémenteur itérateur
  • opérateur! =
  • constructeur par défaut
  • opérateur-> 

Un autre exercice intéressant consiste à transformer cela en un modèle qui fonctionne avec des conteneurs arbitraires. Le code est fondamentalement le même, sauf que vous devez ajouter des annotations typename à quelques endroits. 

Exemple d'utilisation:

int main()
{
    outer_type outer;
    int a = 0, b = 1, c = 2;
    inner_type inner1, inner2;
    inner1.Push_back(&a);
    inner1.Push_back(&b);
    inner2.Push_back(&c);
    outer.Push_back(&inner1);
    outer.Push_back(&inner2);

    my_iterator it(outer.begin(), outer.end());
                e(outer.end(), outer.end());
    for(; it != e; ++it)
        std::cout << **it << "\n";
}

Quelles impressions:

0 1 2

18
Manuel

Un itérateur est juste une classe qui supporte une certaine interface. Au minimum, vous voudrez pouvoir:

  • incrémenter et/ou décrémenter
  • déréférencez-le pour que l'objet soit "pointé"
  • tester pour l'égalité et l'inégalité
  • copier et assigner

Une fois que vous avez une classe capable de le faire de manière judicieuse pour votre collection, vous devez la modifier pour que les fonctions renvoient des itérateurs. Au minimum, vous voudrez

  • une fonction begin () qui retourne une instance de votre nouveau type d'itérateur positionné sur le premier élément
  • une fonction end () qui renvoie un itérateur qui est (éventuellement théoriquement) positionné au-delà de la fin des éléments de votre conteneur
6
anon

Vérifiez la bibliothèque de modèles de vues .

Surtout vérifier 

  1. Vue Union présentant deux conteneurs concaténés.
  2. Vue de la concaténation présentant une collection de conteneurs concaténés.
1
Nitin Bhide

C'est le code le plus simple que j'ai pu produire (pour les itérateurs personnalisés). Notez que je commence seulement à explorer cette région. Ceci appelle la fonction upper_bound intégrée pour effectuer une recherche binaire sur une fonction entière, x^2 à titre d'exemple.

#include <algorithm>
#include <iostream>

using namespace std;

class Iter
{
  public:
  int x;
  Iter() { x = -1; }
  Iter(int a) { x = a; }

  bool operator!=(Iter &i2) const { return x != i2.x; }
  void operator++() { x++; }
  void operator+=(int b) { x += b; }
  int operator-(const Iter &i2) const { return x - i2.x; }
  int operator*() const {
    cout << "calculating for x " << x << endl;
    return x*x;
  }

  typedef random_access_iterator_tag iterator_category;
  typedef int value_type;
  typedef int difference_type;
  typedef int* pointer;
  typedef int& reference;
};

main ()
{
  ios::sync_with_stdio(false);
  cout << upper_bound(Iter(0), Iter(100), 40).x << endl;
}

// :collapseFolds=1:folding=explicit:

Et voici à quoi ressemble la sortie:

calculating for x 50
calculating for x 25
calculating for x 12
calculating for x 6
calculating for x 9
calculating for x 8
calculating for x 7
7
0
Jarekczek