En mi proyecto C ++, tengo dos clases, Particley Contact. En la Particleclase, tengo una variable miembro std::vector<Contact> contactsque contiene todos los contactos de un Particleobjeto, y las funciones miembro correspondientes getContacts()y addContact(Contact cont). Por lo tanto, en "Particle.h", incluyo "Contact.h".
En la Contactclase, me gustaría agregar código al constructor para Contactque se llame Particle::addContact(Contact cont), de modo que contactsse actualice para los dos Particleobjetos entre los que Contactse agrega el objeto. Por lo tanto, tendría que incluir "Particle.h" en "Contact.cpp".
Mi pregunta es si esto es aceptable o buena práctica de codificación y, de lo contrario, cuál sería una mejor manera de implementar lo que estoy tratando de lograr (simplemente, actualizar automáticamente la lista de contactos para una partícula específica cada vez que un nuevo contacto es creado).
Estas clases estarán unidas por una Networkclase que tendrá N partículas ( std::vector<Particle> particles) y Nc contactos ( std::vector<Contact> contacts). Pero quería poder tener funciones como particles[0].getContacts(): ¿está bien tener tales funciones en la Particleclase en este caso, o hay una mejor "estructura" de asociación en C ++ para este propósito (de dos clases relacionadas que se utilizan en otra clase)? .
Es posible que necesite un cambio de perspectiva aquí en cómo me estoy acercando a esto. Dado que las dos clases están conectadas por un Networkobjeto de clase, es típico que la organización de código / clase tenga información de conectividad totalmente controlada por el Networkobjeto (en el sentido de que un objeto Particle no debe conocer sus contactos y, en consecuencia, no debe tener un getContacts()miembro función). Entonces, para saber qué contactos tiene una partícula específica, necesitaría obtener esa información a través del Networkobjeto (por ejemplo, usando network.getContacts(Particle particle)).
¿Sería menos típico (quizás incluso desalentado) el diseño de clase C ++ para que un objeto Particle también tenga ese conocimiento (es decir, tenga múltiples formas de acceder a esa información, ya sea a través del objeto Network o el objeto Particle, lo que sea más conveniente) )?
fuente

Networkobjeto de clase que contieneParticleobjetos yContactobjetos. Con ese conocimiento básico, puedo intentar evaluar si se ajusta o no a mis necesidades específicas, que todavía se están explorando / desarrollando a medida que avanzo en el proyecto.Respuestas:
Hay dos partes en su pregunta.
La primera parte es la organización de los archivos de encabezado C ++ y los archivos fuente. Esto se resuelve mediante el uso de la declaración directa y la separación de la declaración de clase (colocándolos en el archivo de encabezado) y el cuerpo del método (colocándolos en el archivo fuente). Además, en algunos casos se puede aplicar el lenguaje Pimpl ("puntero a la implementación") para resolver casos más difíciles. Utilice punteros de propiedad compartida (
shared_ptr), punteros de propiedad única (unique_ptr) y punteros no propietarios (puntero sin formato, es decir, el "asterisco") de acuerdo con las mejores prácticas.La segunda parte es cómo modelar objetos que están interrelacionados en forma de gráfico . Los gráficos generales que no son DAG (gráficos acíclicos dirigidos) no tienen una forma natural de expresar la propiedad de un árbol. En cambio, los nodos y las conexiones son todos metadatos que pertenecen a un único objeto gráfico. En este caso, no es posible modelar la relación nodo-conexión como agregaciones. Los nodos no "poseen" conexiones; las conexiones no "poseen" nodos. En cambio, son asociaciones, y tanto los nodos como las conexiones son "propiedad" del gráfico. El gráfico proporciona métodos de consulta y manipulación que operan en los nodos y las conexiones.
fuente
particles[0].getContacts(): ¿sugiere en su último párrafo que no debería tener tales funciones en laParticleclase, o que la estructura actual está bien porque están inherentemente relacionadas / asociadasNetwork? ¿Existe una mejor "estructura" de asociación en C ++ en este caso?network.particle[p]tendría un correspondientenetwork.contacts[p]con los índices de sus contactos. De lo contrario, la Red y la Partícula de alguna manera ambos rastrean la misma información.Particleobjeto no debería ser consciente de sus contactos (por lo que no debería tener unagetContacts()función miembro), y que esa información solo debería provenir del interior delNetworkobjeto? ¿Sería un mal diseño de clase C ++ para unParticleobjeto tener ese conocimiento (es decir, tener múltiples formas de acceder a esa información, a través delNetworkobjeto o delParticleobjeto, lo que parezca más conveniente)? Esto último parece tener más sentido para mí, pero tal vez necesito cambiar mi perspectiva sobre esto.Particlesaber algo acerca deContacts oNetworks es que te vincula a una forma específica de representar esa relación. Las tres clases pueden tener que estar de acuerdo. Si, en cambio,Networkes el único que sabe o le importa, esa es solo una clase que debe cambiar si decide que otra representación es mejor.ParticleyContactdebe ser totalmente independiente, y la asociación entre ellos se define por elNetworkobjeto. Solo para estar completamente seguro, esto es (probablemente) lo que @rwong quiso decir cuando escribió: "tanto los nodos como las conexiones son" propiedad del "gráfico. El gráfico proporciona métodos de consulta y manipulación que operan en los nodos y las conexiones". , ¿derecho?Si lo entendí bien, el mismo objeto de contacto pertenece a más de un objeto de partículas, ya que representa algún tipo de contacto físico entre dos o más partículas, ¿verdad?
Entonces, lo primero que creo que es cuestionable es ¿por qué
Particletiene una variable miembrostd::vector<Contact>? Debería ser astd::vector<Contact*>o astd::vector<std::shared_ptr<Contact> >en su lugar.addContactentonces debería tener una firma diferente comoaddContact(Contact *cont)o en suaddContact(std::shared_ptr<Contact> cont)lugar.Esto hace innecesario incluir "Contact.h" en "Particle.h",
class Contactserá suficiente una declaración de "Particle.h" y una inclusión de "Contact.h" en "Particle.cpp".Luego la pregunta sobre el constructor. Quieres algo como
¿Derecho? Este diseño está bien, siempre que su programa siempre conozca las partículas relacionadas en el momento en que se debe crear un objeto de contacto.
Tenga en cuenta que si va por la
std::vector<Contact*>ruta, debe invertir algunas ideas sobre la vida útil y la propiedad de losContactobjetos. Ninguna partícula "posee" sus contactos, un contacto probablemente tendrá que eliminarse solo siParticlese destruyen ambos objetos relacionados . Usar en sustd::shared_ptr<Contact>lugar resolverá este problema automáticamente. O deja que un objeto de "contexto circundante" tome posesión de partículas y contactos (como lo sugiere @rwong), y gestiona su vida útil.fuente
addContact(const std::shared_ptr<Contact> &cont)másaddContact(std::shared_ptr<Contact> cont)?moveparadigmaparticle1.getContacts()yparticle2.getContacts()entregar el mismoContactobjeto que representa el contacto físico entreparticle1yparticle2, y no dos objetos diferentes. Por supuesto, uno podría tratar de diseñar el sistema de una manera que no importe si hay dosContactobjetos disponibles al mismo tiempo que representen el mismo contacto físico. Esto implicaría hacerContactinmutable, pero ¿estás seguro de que esto es lo que quieres?Sí, lo que describe es una forma muy aceptable de garantizar que cada
Contactinstancia esté en la lista de contactos de aParticle.fuente
Lo que has hecho es correcto.
Otra forma ... Si el objetivo es asegurar que todos
Contactestén en una lista, entonces usted podría:Contact(constructores privados),Particleclase,Particlela clase de un amigoContact,Particlecrear un método de fábrica que crea unContactEntonces no tienes que incluir
particle.hencontactfuente
Networkclase, ¿eso cambia la estructura sugerida, o seguiría siendo la misma?Otra opción que puede considerar es hacer que el constructor de contactos que acepte una referencia de partícula tenga una plantilla. Esto permitirá que un contacto se agregue a cualquier contenedor que implemente
addContact(Contact).fuente