Créer mes propres itérateurs

141

J'essaie d'apprendre le C ++ alors pardonnez-moi si cette question démontre un manque de connaissances de base, vous voyez, le fait est que j'ai un manque de connaissances de base.

Je veux de l'aide pour savoir comment créer un itérateur pour une classe que j'ai créée.

J'ai une classe «Shape» qui contient un conteneur de points. J'ai une classe 'Piece' qui fait référence à une forme et définit une position pour la forme. La pièce n'a pas de forme, elle fait simplement référence à une forme.

Je veux que cela ressemble à Piece est un conteneur de points qui sont les mêmes que ceux de la forme qu'il référence mais avec le décalage de la position de la pièce ajouté.

Je veux pouvoir parcourir les points de la pièce comme si Piece était un conteneur lui-même. J'ai fait un peu de lecture et je n'ai rien trouvé qui m'ait aidé. Je serais très reconnaissant pour tous les conseils.

Howard May
la source
6
La publication d'un exemple de code aiderait à décrire ce que vous faites mieux qu'un simple texte anglais.
Greg Rogers le
3
La création d'itérateurs personnalisés n'est probablement pas un top de base, du moins intermédiaire.
ldog le

Réponses:

41

Vous devez utiliser Boost.Iterators. Il contient un certain nombre de modèles et de concepts pour implémenter de nouveaux itérateurs et adaptateurs pour les itérateurs existants. J'ai écrit un article sur ce sujet même ; c'est dans le magazine ACCU de décembre 2008. Il traite d'une solution élégante (IMO) pour exactement votre problème: exposer des collections de membres à partir d'un objet, en utilisant Boost.Iterators.

Si vous souhaitez utiliser uniquement le stl, le livre Josuttis contient un chapitre sur l'implémentation de vos propres itérateurs STL.

Roel
la source
3
Juste une remarque mineure: le livre parle de la bibliothèque standard C ++, pas de la STL - celles-ci sont différentes, mais deviennent beaucoup confuses (je suis /
étais
62

/ EDIT: Je vois, un propre itérateur est en fait nécessaire ici (j'ai d'abord mal lu la question). Pourtant, je laisse le code ci-dessous reposer car il peut être utile dans des circonstances similaires.


Un itérateur propre est-il réellement nécessaire ici? Il suffit peut-être de transmettre toutes les définitions requises au conteneur contenant les points réels:

// Your class `Piece`
class Piece {
private:
    Shape m_shape;

public:

    typedef std::vector<Point>::iterator iterator;
    typedef std::vector<Point>::const_iterator const_iterator;

    iterator begin() { return m_shape.container.begin(); }

    const_iterator begin() const { return m_shape.container.begin(); }

    iterator end() { return m_shape.container.end(); }

    const_iterator end() const { return m_shape.const_container.end(); }
}

Cela suppose que vous utilisez un en vectorinterne, mais le type peut facilement être adapté.

Konrad Rudolph
la source
peut-être qu'il veut utiliser l'algorithme STL ou des fonctionnalités fonctionnelles contre sa classe ...
gbjbaanb
2
La question d'origine dit en fait que l'itérateur du conteneur de pièces doit modifier les valeurs lors de leur renvoi. Cela nécessiterait un itérateur séparé, bien qu'il devrait probablement être hérité ou autrement obtenu principalement de l'original.
workmad3
@gbjbaanb: La bonne chose à propos de mon code est qu'il peut être utilisé par les algorithmes STL.
Konrad Rudolph le
1
Quelques années plus tard et cela fait toujours partie des meilleurs résultats sur google ... Il est maintenant possible de généraliser cela en faisant quelque chose comme ceci:auto begin() -> decltype(m_shape.container.begin()) { return m_shape.container.begin(); }
user2962533
20

Ici, Concevoir une STL comme un conteneur personnalisé est un excellent article qui explique certains des concepts de base sur la façon dont une classe STL comme un conteneur peut être conçue avec la classe itérateur correspondante. Itérateur inversé (un peu plus difficile) reste cependant un exercice :-)

HTH,

Abhay
la source
15

Vous pouvez lire cet article ddj

En gros, héritez de std :: iterator pour faire la plupart du travail à votre place.

gbjbaanb
la source
2
Notez qu'il std::iteratorest marqué comme obsolète à partir de C ++ 17.
mandrake
2

L'écriture d'itérateurs personnalisés en C ++ peut être assez verbeuse et complexe à comprendre.

Comme je ne pouvais pas trouver un moyen minimal d'écrire un itérateur personnalisé, j'ai écrit cet en-tête de modèle qui pourrait aider. Par exemple, pour rendre la Piececlasse itérable:

#include <iostream>
#include <vector>

#include "iterator_tpl.h"

struct Point {
  int x;
  int y;
  Point() {}
  Point(int x, int y) : x(x), y(y) {}
  Point operator+(Point other) const {
    other.x += x;
    other.y += y;
    return other;
  }
};

struct Shape {
  std::vector<Point> vec;
};

struct Piece {
  Shape& shape;
  Point offset;
  Piece(Shape& shape, int x, int y) : shape(shape), offset(x,y) {}

  struct it_state {
    int pos;
    inline void next(const Piece* ref) { ++pos; }
    inline void begin(const Piece* ref) { pos = 0; }
    inline void end(const Piece* ref) { pos = ref->shape.vec.size(); }
    inline Point get(Piece* ref) { return ref->offset + ref->shape.vec[pos]; }
    inline bool cmp(const it_state& s) const { return pos != s.pos; }
  };
  SETUP_ITERATORS(Piece, Point, it_state);
};

Ensuite, vous pourrez l'utiliser comme un conteneur STL normal:

int main() {
  Shape shape;
  shape.vec.emplace_back(1,2);
  shape.vec.emplace_back(2,3);
  shape.vec.emplace_back(3,4);

  Piece piece(shape, 1, 1);

  for (Point p : piece) {
    std::cout << p.x << " " << p.y << std::endl;
    // Output:
    // 2 3
    // 3 4
    // 4 5
  }

  return 0;
}

Il permet également d'ajouter d'autres types d'itérateurs comme const_iteratorou reverse_const_iterator.

J'espère que cela aide.

VinGarcia
la source
1

La solution à votre problème n'est pas la création de vos propres itérateurs, mais l'utilisation de conteneurs et d'itérateurs STL existants. Stockez les points de chaque forme dans un conteneur comme un vecteur.

class Shape {
    private:
    vector <Point> points;

Ce que vous faites à partir de là dépend de votre conception. La meilleure approche consiste à parcourir les points dans les méthodes à l'intérieur de Shape.

for (vector <Point>::iterator i = points.begin(); i != points.end(); ++i)
    /* ... */

Si vous avez besoin d'accéder à des points en dehors de Shape (cela pourrait être une marque d'une conception déficiente), vous pouvez créer des méthodes Shape qui renverront les fonctions d'accès à l'itérateur pour les points (dans ce cas, créez également un typedef public pour le conteneur de points). Regardez la réponse de Konrad Rudolph pour plus de détails sur cette approche.

Diomidis Spinellis
la source
3
Il devra toujours créer son propre itérateur qui transmettra les requêtes à Piece aux formes qui se trouvent dans cette Piece. Les itérateurs personnalisés sont ici un excellent outil et très élégant à utiliser.
Roel le