如何解决我的C ++代码中的类相互依赖性?


10

在我的C ++项目中,我有两个类,ParticleContact。在Particle该类中,我有一个成员变量std::vector<Contact> contacts,其中包含Particle对象的所有触点以及相应的成员函数getContacts()addContact(Contact cont)。因此,在“ Particle.h”中,我包括“ Contact.h”。

Contact该类中,我想将代码添加到构造函数中Contact,以调用Particle::addContact(Contact cont),以便contacts针对Particle在其间Contact添加对象的两个对象进行更新。因此,我必须在“ Contact.cpp”中包含“ Particle.h”。

我的问题是这是否可接受/良好的编码习惯,如果不能,那么哪种更好的方式来实现我要实现的目标(简单地说,每当有新联系人时,自动更新特定粒子的联系人列表)被建造)。


这些类别将由Network具有N个粒子(std::vector<Particle> particles)和Nc个接触(std::vector<Contact> contacts)的类别联系在一起。但是我希望能够拥有类似的功能particles[0].getContacts()Particle在这种情况下可以在类中使用这样的功能,还是为此目的在C ++中有更好的关联“结构”(在另一个类中使用两个相关类) 。


在此方面,我可能需要转变看法。由于这两个类是由一个Network类对象连接的,因此典型的代码/类组织是具有完全由该Network对象控制的连接信息(因为Particle对象不应知道其联系,因此,它不应具有getContacts()成员)功能)。然后,为了知道特定粒子具有什么接触,我将需要通过Network对象(例如使用network.getContacts(Particle particle))获得该信息。

具有粒子粒子知识的C ++类设计是否也不太典型(也许甚至不鼓励),也就是(具有通过网络对象或粒子粒子访问信息的多种方式),无论哪种方式看起来更方便)?


4
以下是cppcon 2017的主题-标头的三层:youtu.be/su9ittf-ozk
Robert Andrzejuk

3
除非您可以陈述自己的特定评估标准,
罗伯特·哈维

感谢您的编辑,尽管将措辞更改为“典型”只是使其成为受欢迎的问题。出于某种原因,为什么要以一种或另一种方式进行编码,并且尽管普及程度可以表明一种技术是“好”(对于“好”的某种定义),但是它也可以表明是一种耕作。
罗伯特·哈维

@RobertHarvey我在最后一节中删除了“更好”和“不好”。我想当您有一个Network包含Particle对象和Contact对象的类对象时,我正在寻求一种典型的方法(也许甚至是被鼓励/鼓励)。有了这些基础知识,我便可以尝试评估它是否适合我的特定需求,随着我在项目中的进行,这些需求仍在探索/开发中。
AnInquiringMind

@RobertHarvey我想我是一个新手,可以完全从头开始编写C ++项目,并且可以学习“典型”和“受欢迎”的内容。希望我能在某个时候获得足够的见识,从而能够理解为什么另一个实现实际上更好,但是就目前而言,我只是想确保我不会以完全笨拙的方式来解决这个问题!
AnInquiringMind

Answers:


17

您的问题分为两部分。

第一部分是C ++头文件和源文件的组织。这可以通过使用前向声明以及将类声明(将它们放入头文件中)和方法主体(将它们放入源文件中)分开来解决。此外,在某些情况下,可以应用Pimpl习惯用法(“实现的指针”)来解决更困难的情况。根据最佳实践shared_ptr,使用共享所有权指针(),单所有权指针(unique_ptr)和非所有权指针(原始指针,即“星号”)。

第二部分是如何以的形式对相互关联的对象进行建模。不是 DAG的常规图(有向无环图)没有表达树状所有权的自然方法。相反,节点和连接都是属于单个图形对象的所有元数据。在这种情况下,无法将节点连接关系建模为聚合。节点不“拥有”连接;连接不“拥有”节点。相反,它们是关联,并且节点和连接都由图“拥有”。该图提供了对节点和连接进行操作的查询和操作方法。


感谢您的回复!我实际上确实有一个Network类,它将具有N个粒子和Nc个触点。但是我希望能够拥有类似的功能particles[0].getContacts()–您是否在上一段中建议我不应在此类中使用此类功能Particle,或者当前结构尚可,因为它们之间存在固有的关联/关联Network?在这种情况下,C ++中是否存在更好的关联“结构”?
AnInquiringMind

1
通常,网络负责了解对象之间的关系。例如,如果使用邻接表,则粒子network.particle[p]将具有network.contacts[p]与其联系人的索引相对应的对象。否则,网络和粒子都会以某种方式跟踪相同的信息。
无用的

@没用的是的,那是我不确定如何进行的地方。所以您说Particle对象不应该知道其联系(因此我不应该具有getContacts()成员函数),并且该信息应该仅来自Network对象内部?具有Particle对象知识的对象(例如,通过Network对象或Particle对象,以多种方式访问​​都更方便)有多种知识来访问对象,这将是不好的C ++类设计吗?后者对我来说似乎更有意义,但也许我需要改变对此的看法。
AnInquiringMind

1
@PhysicsCodingEnthusiast:ParticleContacts或Networks有任何了解的问题是,它将您绑定到一种表示该关系的特定方式。这三个类别可能都必须同意。相反,如果Network只有一位知道或关心的人,那么如果您认为另一种表示更好的话,那就只有一个班级需要改变。
cHao

@cHao好的,这很有道理。因此,Particle并且Contact应该完全分开,并且它们之间的关联由Network对象定义。完全可以肯定的是,这是@rwong可能在@rwong写道时表示的意思:“节点和连接都属于该图”。该图提供了对节点和连接进行操作的查询和操作方法。 , 对?
AnInquiringMind

5

如果我理解正确,那么同一接触对象属于多个粒子对象,因为它表示两个或多个粒子之间的某种物理接触,对吗?

因此,我认为值得怀疑的第一件事就是为什么Particle要有一个成员变量std::vector<Contact>?应该是a std::vector<Contact*>或a std::vector<std::shared_ptr<Contact> >addContact那么应该具有不同的签名,例如addContact(Contact *cont)addContact(std::shared_ptr<Contact> cont)替代。

这样就不必在“ Particle.h”中包含“ Contact.h”,在“ Particle.h”中包含正向声明,而class Contact在“ Particle.cpp”中包含“ Contact.h”就足够了。

然后是关于构造函数的问题。你想要类似的东西

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

对?只要您的程序在必须创建接触对象的时间点始终知道相关的粒子,此设计就可以。

请注意,如果走这std::vector<Contact*>条路线,则必须花一些时间考虑Contact对象的寿命和所有权。没有粒子“拥有”其接触,只有两个相关Particle对象都被破坏时,才可能必须删除该接触。使用std::shared_ptr<Contact>代替将自动为您解决此问题。或者,让“周围环境”对象获取粒子和接触的所有权(如@rwong所建议),并管理其生命周期。


我没有看到的好处addContact(const std::shared_ptr<Contact> &cont)addContact(std::shared_ptr<Contact> cont)
卡莱斯(Caleth),

@Caleth:在这里讨论了这一点:stackoverflow.com/questions/3310737/…- “ const”在这里并不是很重要,但是按引用传递对象(按值传递标量)是C ++中的标准习惯用法。
布朗

2
这些答案中的许多似乎都来自move范式
Caleth,2017年

@Caleth:好的,为了让所有的Nickpickers开心,我更改了答案中这个不重要的部分。
布朗

1
@PhysicsCodingEnthusiast:没有,这是最重要的有关使particle1.getContacts()particle2.getContacts()提供相同的Contact表示之间的物理接触对象particle1particle2,而不是两个不同的对象。当然,可以尝试以一种方式设计系统,这与是否有两个Contact对象同时代表相同的物理接触无关紧要。这将涉及使Contact不可变,但是您确定这就是您想要的吗?
布朗

0

是的,您所描述的是确保每个Contact实例都在的联系人列表中的一种非常可接受的方式Particle


感谢您的回复。我已经读过一些建议,避免使用一对相互依赖的类(例如,在MS Joshi的“ C ++设计模式和衍生物定价”中),但是显然这不一定正确吗?出于好奇,也许是否存在另一种无需相互依赖而实现此自动更新的方法?
AnInquiringMind

4
@PhysicsCodingEnthusiast:具有相互依赖的类会造成各种困难,您应该避免这些困难。但是有时,两个类之间的联系如此紧密,以至于消除它们之间的相互依赖性比相互依赖性本身会引起更多的问题。
Bart van Ingen Schenau

0

您所做的是正确的。

另一种方法...如果目的是确保每个Contact列表都在列表中,则可以:

  • 阻止Contact(私有构造函数)的创建,
  • 转发声明Particle类,
  • 使Particle班级成为的朋友Contact
  • Particle创建工厂方法中创建一个Contact

然后,你不必包括particle.hcontact


感谢您的回复!这似乎是实现此目标的一种有用方法。我只是想知道,在编辑有关Network班级的第一个问题时,这是否会改变建议的结构,还是会保持不变?
AnInquiringMind

更新问题后,它将改变范围。...现在,您正在询问应用程序的体系结构,而以前是关于技术问题的。
罗伯特·安德泽胡克

0

您可能考虑的另一种选择是使Contact构造函数接受模板化的Particle引用。这将允许Contact将自身添加到实现的任何容器中addContact(Contact)

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.