2010-11-11 6 views
3

J'ai rencontré ce problème plusieurs fois et je voudrais savoir s'il existe une méthode ou un modèle simple que je peux utiliser pour le résoudre. Imaginez une structure arborescente dans laquelle chaque nœud contient un conteneur STL (vecteur) de pointeurs vers des enfants. Je veux que le code client puisse traverser cette arborescence et parcourir les conteneurs enfants pour accéder aux autres parties.Comment parcourir à travers un conteneur dans une autre classe?

Mon problème est que je veux maintenir l'encapsulation pour mes nœuds tout en permettant aux clients de voir facilement tous les enfants pour ce nœud. Je veux également m'assurer que si un client obtient une référence const au nœud racine dans l'arbre, alors tous les accès aux parties suivantes de l'arbre sont aussi const. Dois-je essayer de créer une classe d'itérateur pour mon type de nœud, avoir une méthode de nœud pour renvoyer des itérateurs vectoriels, ou y a-t-il un modèle plus élégant qui me manque?

Edit: Je tiens à souligner à nouveau que si je vois quelques bonnes idées présentées, j'ai des conteneurs de pointeurs à d'autres nœuds. Renvoyer un vector<node *>::const_iterator n'empêchera pas les clients d'appeler des méthodes non-const sur le nœud. Il protège seulement les pointeurs eux-mêmes de pointer sur différents objets.

Répondre

4

Quelque chose comme cela est généralement suffisant:

class node 
{ 
public: 
    typedef std::vector<node> node_list; 
    typedef node_list::const_iterator const_iterator; 

    const_iterator begin() const { return children_.begin(); } 
    const_iterator end() const { return children_.end(); } 

private: 
    node_list children_; 
} 

Cela vous permet de changer le type de conteneur sous-jacent sans changement de code qui itère sur un enfant de node.

Cela présente l'inconvénient qu'il fuit un détail de mise en œuvre parce que le code qui utilise votre node::const_iterator savent qu'il est un itérateur d'accès aléatoire (parce que std::vector:: const_iterator est un itérateur d'accès aléatoire), de sorte que vous pourriez avoir du mal à passer à un conteneur cela ne supportait pas l'accès aléatoire.

Si vous souhaitez contrôler vous-même la catégorie de l'itérateur, vous voudrez probablement créer votre propre classe iterator qui fournit le comportement exact que vous voulez fournir.

+0

Est-ce que cela fonctionnera aussi si node_list est un vecteur de pointeurs vers des nœuds, et je veux que le client ait seulement des pointeurs vers des nœuds const? –

+0

"Le code sait qu'il s'agit d'un itérateur à accès aléatoire" point intéressant, mais le code sait également qu'il s'agit d'un vecteur: "const_iterator". Cependant, il est beaucoup plus facile d'utiliser 'operator +' sans réfléchir, que d'assigner l'itérateur à 'vector :: const_iterator' au lieu de' node :: const_iterator' sans réfléchir. Je me demande s'il y a un cas pour un adaptateur d'itérateur qui ne fait rien d'autre que «rétrograder» l'itérateur qu'il enveloppe à une catégorie spécifique. Ensuite, vous pouvez retourner 'make_forward_iterator (children_.begin())', ou un tel. –

+0

@Steve: Puisque la catégorie fait partie du type de l'itérateur, vous devriez faire la conversion dans le typedef. Je pense que vous pourriez le faire assez facilement en utilisant une classe 'template forward_iterator_wrapper: private RealIteratorT {};'. Il pourrait y avoir des problèmes. Je n'y ai pas réfléchi en profondeur. –

0

Pour traverser l'arbre entier, vous devez créer votre propre classe d'itérateur, pour juste traverser les enfants, vous pouvez retourner en toute sécurité les itérateurs vectoriels.

4

Ceci n'est pas une réponse directe à votre question, mais plutôt une approche alternative. Plutôt que de faire exécuter le spectacle par le code client, vous pouvez opter pour control inversion en enregistrant un foncteur de rappel. Par exemple:

// Derive from this class to create a visitor 
class AbstractVisitor 
{ 
public: 
    virtual void operator() (const T &) = 0; 
}; 


// Your recursive data-structure class 
class MyClass 
{ 
public: 
    void walk(AbstractVisitor &v) const 
    { 
     // Call the client callback 
     v(payload); 
     for (std::vector<MyClass>::const_iterator it = children.begin(); 
      it != children.end(); ++it) 
     { 
      // Recurse 
      it->walk(v); 
     } 
    } 

private: 
    T payload; // Some sort of payload associated with the class 
    std::vector<MyClass> children; 
}; 


// You could have different visitor classes to do different things 
class MyVisitor : public AbstractVisitor 
{ 
public: 
    virtual void operator() (const T &t) 
    { 
     // Do something with t 
    } 
} 


int main() 
{ 
    MyClass m; 
    MyVisitor v; 
    ... 
    m.walk(v); 
} 

Encapsulation complète réalisée!

+0

Modèle de visiteur! : -O (Eh bien, quelque chose en rapport avec ça, au moins.) –

+2

Personnellement, en C++, je voudrais que 'walk' soit un modèle de fonction avec un paramètre functor. A défaut (parce que parfois vous ne voulez pas de modèles aux limites de l'interface), je voudrais qu'un pointeur de données utilisateur accompagne mon pointeur de fonction, ou une interface avec une fonction virtuelle. Sinon, la marche est limitée aux opérations qui n'ont pas d'état sauf l'état global, et ne renvoie aucune valeur (car elle est vide). C'est-à-dire, mal conçus ;-) –

+0

@Steve: oui, absolument; idéalement, vous voudriez être en mesure de lui fournir des foncteurs. Mais cela semblait un peu compliqué à jeter ensemble pour l'extrait de code ci-dessus! –

Questions connexes