web-dev-qa-db-fra.com

Comment résoudre l'interdépendance des classes dans mon code C ++?

Dans mon projet C++, j'ai deux classes, Particle et Contact. Dans la classe Particle, j'ai une variable membre std::vector<Contact> contacts Qui contient tous les contacts d'un objet Particle et les fonctions membres correspondantes getContacts() et addContact(Contact cont). Ainsi, dans "Particle.h", j'inclus "Contact.h".

Dans la classe Contact, je voudrais ajouter du code au constructeur pour Contact qui appellera Particle::addContact(Contact cont), afin que contacts soit mis à jour à la fois pour le Particle objets entre lesquels l'objet Contact est ajouté. Ainsi, je devrais inclure "Particle.h" dans "Contact.cpp".

Ma question est de savoir si cela est acceptable/bonne pratique de codage et, sinon, quelle serait une meilleure façon de mettre en œuvre ce que j'essaie d'atteindre (simplement mettre à jour automatiquement la liste des contacts pour une particule spécifique chaque fois qu'un nouveau contact est créé).


Ces classes seront liées entre elles par une classe Network qui aura N particules (std::vector<Particle> particles) Et Nc contacts (std::vector<Contact> contacts). Mais je voulais pouvoir avoir des fonctions comme particles[0].getContacts() - est-ce correct d'avoir de telles fonctions dans la classe Particle dans ce cas, ou y a-t-il une meilleure "structure" d'association en C++ pour cet objectif (de deux classes apparentées utilisées dans une autre classe).


J'ai peut-être besoin d'un changement de perspective ici dans la façon dont j'aborde cette question. Étant donné que les deux classes sont connectées par un objet de classe Network, est-ce une organisation de code/classe typique d'avoir des informations de connectivité entièrement contrôlées par l'objet Network (en ce qu'un objet Particle ne devrait pas être au courant ses contacts et, par conséquent, il ne doit pas avoir une fonction membre getContacts()). Ensuite, afin de savoir quels contacts a une particule spécifique, j'aurais besoin d'obtenir ces informations via l'objet Network (par exemple, en utilisant network.getContacts(Particle particle)).

Serait-il moins typique (peut-être même découragé) de concevoir une classe C++ pour un objet Particle d'avoir également ces connaissances (c'est-à-dire, avoir plusieurs façons d'accéder à ces informations - via l'objet Network ou l'objet Particle, selon ce qui semble le plus pratique) )?

10
AnInquiringMind

Votre question comporte deux parties.

La première partie est l'organisation des fichiers d'en-tête C++ et des fichiers source. Ceci est résolu en utilisant la déclaration directe et la séparation de la déclaration de classe (en les mettant dans le fichier d'en-tête) et du corps de la méthode (en les mettant dans le fichier source ). De plus, dans certains cas, on peut appliquer le idiome Pimpl ("pointeur vers l'implémentation") pour résoudre des cas plus difficiles. Utilisez des pointeurs de propriété partagée (shared_ptr), pointeurs à propriété unique (unique_ptr), et les pointeurs non propriétaires (pointeur brut, c'est-à-dire "l'astérisque") selon les meilleures pratiques.

La deuxième partie est de savoir comment modéliser des objets qui sont interdépendants sous la forme d'un graphique . Les graphes généraux qui ne sont pas les DAG (graphes acycliques dirigés) n'ont pas un moyen naturel d'exprimer la propriété arborescente. Au lieu de cela, les nœuds et les connexions sont toutes des métadonnées qui appartiennent à un seul objet graphique. Dans ce cas, il n'est pas possible de modéliser la relation nœud-connexion sous forme d'agrégations. Les nœuds ne "possèdent" pas de connexions; les connexions ne "possèdent" pas de nœuds. Au lieu de cela, ce sont des associations, et les nœuds et les connexions appartiennent au graphique. Le graphique fournit des méthodes de requête et de manipulation qui opèrent sur les nœuds et les connexions.

17
rwong

Si je vous comprends bien, le même objet de contact appartient à plus d'un objet de particules, car il représente une sorte de contact physique entre deux ou plusieurs particules, non?

Donc la première chose qui, je pense, est discutable est pourquoi Particle a une variable membre std::vector<Contact>? Ce devrait être un std::vector<Contact*> Ou un std::vector<std::shared_ptr<Contact> > À la place. addContact devrait alors avoir une signature différente comme addContact(Contact *cont) ou addContact(std::shared_ptr<Contact> cont) à la place.

Il n'est donc pas nécessaire d'inclure "Contact.h" dans "Particle.h", une déclaration directe de class Contact Dans "Particle.h" et une inclusion de "Contact.h" dans "Particle.cpp" sera suffisant.

Ensuite, la question sur le constructeur. Tu veux quelque chose comme

 Contact(Particle &p1, Particle &p2)
 {
      p1.addContact(this);
      p2.addContact(this);
 }

Droite? Cette conception est correcte, tant que votre programme connaît toujours les particules associées au moment où un objet contact doit être créé.

Notez que si vous suivez la route std::vector<Contact*>, Vous devez réfléchir à la durée de vie et à la propriété des objets Contact. Aucune particule ne "possède" ses contacts, un contact ne devra probablement être supprimé que si les deux objets Particle associés sont détruits. L'utilisation de std::shared_ptr<Contact> À la place résoudra automatiquement ce problème. Ou vous laissez un objet "contexte environnant" s'approprier les particules et les contacts (comme suggéré par @rwong) et gérer leur durée de vie.

5
Doc Brown

Une autre option que vous pourriez envisager est de faire du constructeur Contact qui accepte une référence de particule un modèle. Cela permettra à un contact de s'ajouter à n'importe quel conteneur qui implémente addContact(Contact).

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
0
Erroneous

Oui, ce que vous décrivez est un moyen très acceptable de garantir que chaque instance de Contact figure dans la liste des contacts d'un Particle.

Ce que vous avez fait est correct.

Une autre façon ... Si le but est de s'assurer que chaque Contact est dans une liste, alors vous pourriez:

  • bloquer la création de Contact (constructeurs privés),
  • déclarer en avant la classe Particle,
  • faire de Particle classe un ami de Contact,
  • dans Particle créer une méthode d'usine qui crée un Contact

Vous n'avez alors pas besoin d'inclure particle.h dans contact

0
Robert Andrzejuk