Как разрешить взаимозависимость классов в моем коде C ++?

10

В моем проекте C ++ у меня есть два класса, Particleи Contact. В Particleклассе, у меня есть переменная - член std::vector<Contact> contacts, содержащий все контакты Particleобъекта, а также соответствующие функции - члены getContacts()и addContact(Contact cont). Таким образом, в «Particle.h» я включаю «Contact.h».

В Contactклассе я хотел бы добавить код в конструктор, Contactкоторый будет вызывать Particle::addContact(Contact cont), чтобы contactsон обновлялся для обоих Particleобъектов, между которыми Contactдобавляется объект. Таким образом, я должен был бы включить «Particle.h» в «Contact.cpp».

Мой вопрос заключается в том, является ли это приемлемой / хорошей практикой кодирования и, если нет, каков будет лучший способ реализовать то, что я пытаюсь достичь (проще говоря, автоматически обновляя список контактов для конкретной частицы при каждом новом контакте создано).


Эти классы будут связаны вместе 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 ++ для объекта Particle также обладать этими знаниями (т. Е. Иметь несколько способов доступа к этой информации - через объект Network или объект Particle, в зависимости от того, что кажется более удобным) )?

AnInquiringMind
источник
4
Вот доклад cppcon 2017 - «Три слоя заголовков»: youtu.be/su9ittf-ozk
Роберт
3
Вопросы, содержащие такие слова, как «лучший», «лучший» и «приемлемый», не подлежат обсуждению, если вы не можете указать свои конкретные критерии оценки.
Роберт Харви
Спасибо за редактирование, хотя изменение вашей формулировки на «типичное» просто делает вопрос популярности. Существуют причины, по которым кодирование выполняется тем или иным образом, и хотя популярность может указывать на то, что метод является «хорошим» (для некоторого определения «хороший»), он также может указывать на получение груза.
Роберт Харви
@RobertHarvey Я удалил «лучше» и «плохо» в своем последнем разделе. Я предполагаю, что я прошу типичный (возможно, даже одобренный / поощряемый) подход, когда у вас есть Networkобъект класса, который содержит Particleобъекты и Contactобъекты. Обладая этими базовыми знаниями, я могу затем попытаться оценить, соответствует ли это моим конкретным потребностям, которые все еще изучаются / разрабатываются по мере продвижения в проекте.
AnInquiringMind
@RobertHarvey Я полагаю, что я достаточно новичок, чтобы писать проекты на С ++ с нуля, и я хорошо разбираюсь в том, что является «типичным» и «популярным». Надеюсь, в какой-то момент я получу достаточно информации, чтобы понять, почему другая реализация на самом деле лучше, но сейчас я просто хочу убедиться, что я не подхожу к этому совершенно безрассудно!
AnInquiringMind

Ответы:

17

В вашем вопросе есть две части.

Первая часть - это организация заголовочных файлов C ++ и исходных файлов. Это решается с помощью предварительного объявления и разделения объявления класса (помещая их в файл заголовка) и тела метода (помещая их в исходный файл). Кроме того, в некоторых случаях можно применять идиому Pimpl («указатель на реализацию») для решения более сложных случаев. Используйте указатели совместного владения ( shared_ptr), указатели единоличного владения ( unique_ptr) и не владеющие указатели (необработанный указатель, то есть «звездочка») в соответствии с лучшими практиками.

Вторая часть состоит в том, как моделировать объекты, которые связаны между собой в форме графика . Обычные графы, которые не являются группами DAG (ориентированные ациклические графы), не имеют естественного способа выражения древовидной принадлежности. Вместо этого все узлы и соединения являются метаданными, которые принадлежат одному объекту графа. В этом случае невозможно смоделировать отношения узел-соединение как агрегации. Узлы не «владеют» соединениями; соединения не «владеют» узлами. Вместо этого они являются ассоциациями, и оба узла и соединения «принадлежат» графу. На графике представлены методы запросов и манипуляций, которые работают с узлами и соединениями.

rwong
источник
Спасибо за ответ! У меня действительно есть класс Network, который будет иметь N частиц и Nc-контакты. Но я хотел иметь возможность иметь такие функции, как particles[0].getContacts()- вы предлагаете в своем последнем абзаце, что у меня не должно быть таких функций в Particleклассе, или что текущая структура в порядке, потому что они по своей природе связаны / связаны через Network? Есть ли лучшая ассоциация "структура" в C ++ в этом случае?
AnInquiringMind
1
Как правило, Сеть отвечает за знание отношений между объектами. Например, если вы используете список смежности, частица network.particle[p]будет иметь соответствие network.contacts[p]с индексами своих контактов. В противном случае Сеть и Частица каким-то образом отслеживают одну и ту же информацию.
бесполезно
@ Бесполезно Да, вот где я не уверен, что делать дальше. Итак, вы говорите, что Particleобъект не должен знать о своих контактах (поэтому у меня не должно быть getContacts()функции-члена), и что эта информация должна поступать только изнутри Networkобъекта? Будет ли плохой дизайн класса C ++ для Particleобъекта обладать такими знаниями (то есть иметь несколько способов доступа к этой информации - через Networkобъект или Particleобъект, в зависимости от того, что кажется более удобным)? Последнее, кажется, имеет больше смысла для меня, но, возможно, мне нужно изменить свою точку зрения на это.
AnInquiringMind
1
@PhysicsCodingEnthusiast: проблема со Particleзнанием чего-либо о Contacts или Networks состоит в том, что это связывает вас с определенным способом представления этих отношений. Все три класса, возможно, придется договориться. Если вместо этого Networkединственный, кто знает или заботится, это только один класс, который нужно изменить, если вы решите, что другое представление лучше.
cHao
@cHao Хорошо, это имеет смысл. Так Particleи Contactдолжно быть совершенно отдельно, и связь между ними определяется Networkобъектом. Просто чтобы быть полностью уверенным, это (вероятно) то, что имел в виду @rwong, когда писал (а), что «узлы и соединения« принадлежат »графу. Граф предоставляет методы запросов и манипуляций, которые работают с узлами и соединениями». , правильно?
AnInquiringMind
5

Если я вас правильно понял, один и тот же контактный объект принадлежит более чем одному объекту частиц, поскольку он представляет собой своего рода физический контакт между двумя или более частицами, верно?

Итак, первое, что я считаю сомнительным, - это почему Particleпеременная-член std::vector<Contact>? Это должно быть std::vector<Contact*>или std::vector<std::shared_ptr<Contact> >вместо. addContactтогда должна иметь другую подпись как addContact(Contact *cont)или addContact(std::shared_ptr<Contact> cont)вместо.

Это делает ненужным включение «Contact.h» в «Particle.h», предварительное объявление class Contactв «Particle.h» и включение «Contact.h» в «Particle.cpp» будет достаточно.

Тогда вопрос о конструкторе. Вы хотите что-то вроде

 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: хорошо, чтобы все придирки были счастливы, я изменил эту довольно незначительную часть моего ответа.
Док Браун
1
@PhysicsCodingEnthusiast: нет, это прежде всего создание particle1.getContacts()и particle2.getContacts()доставка одного и того же Contactобъекта, представляющего физический контакт между particle1и particle2, а не двух разных объектов. Конечно, можно попытаться спроектировать систему таким образом, чтобы не имело значения, если одновременно доступны два Contactобъекта, представляющих один и тот же физический контакт. Это должно было бы сделать Contactнеизменным, но вы уверены, что это то, что вы хотите?
Док Браун
0

Да, то, что вы описываете, является очень приемлемым способом гарантировать, что каждый Contactэкземпляр находится в списке контактов a Particle.

Барт ван Инген Шенау
источник
Спасибо за ответ. Я читал некоторые предложения о том, что следует избегать наличия пары взаимозависимых классов (например, в статье «Шаблоны проектирования C ++ и ценообразование производных» М.С. Джоши), но, по-видимому, это не обязательно правильно? Из любопытства, возможно, есть ли другой способ реализовать это автоматическое обновление без необходимости взаимозависимости?
AnInquiringMind
4
@PhysicsCodingEnthusiast: Наличие взаимозависимых классов создает все виды трудностей, и вы должны стараться их избегать. Но иногда два класса настолько тесно связаны друг с другом, что устранение взаимозависимости между ними вызывает больше проблем, чем сама взаимозависимость.
Барт ван Инген Шенау
0

То, что вы сделали, правильно.

Другой способ ... Если цель состоит в том, чтобы каждый из Contactних был в списке, то вы могли бы:

  • блок создания Contact(частные конструкторы),
  • вперед объявляю Particleкласс,
  • сделать Particleкласс другом Contact,
  • в Particleсоздании фабричного метода, который создаетContact

Тогда вам не нужно включать particle.hвcontact

Роберт Анджейюк
источник
Спасибо за ответ! Это кажется полезным способом реализации этого. Просто интересно, с моим редактированием к первоначальному вопросу, касающемуся Networkкласса, это меняет предложенную структуру, или оно все равно будет таким же?
AnInquiringMind
После того как Вы обновили свой вопрос, он меняет сферу. ... Теперь вы спрашиваете об архитектуре вашего приложения, когда раньше речь шла о технической проблеме.
Роберт
0

Другой вариант, который вы могли бы рассмотреть, - создать конструктор Contact, который принимает шаблонную ссылку на Particle. Это позволит Контакту добавить себя в любой реализуемый контейнер addContact(Contact).

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
ошибочный
источник