Як вирішити взаємозалежність класу в моєму коді 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об'єктом (у тому, що об'єкт Частинки не повинен знати про свої контакти, а отже, він не повинен мати getContacts()члена функція). Потім, щоб знати, які контакти має конкретна частинка, мені потрібно було б отримати цю інформацію через Networkоб'єкт (наприклад, за допомогою network.getContacts(Particle particle)).

Чи було б менш типовим (можливо, навіть відстороненим) дизайном класу C ++ для об’єкта Particle, а також мати ці знання (тобто мати декілька способів доступу до цієї інформації - або через мережевий об'єкт, або через предмет частинок, що б не здавалося зручнішим) )?


4
Ось бесіда від cppcon 2017 - Три шари заголовків: youtu.be/su9ittf-ozk
Роберт Анджежук

3
Питання, що містять слова типу "найкращий", "кращий" та "прийнятний", не відповідають, якщо ви не можете вказати свої конкретні критерії оцінювання.
Роберт Харві

Дякуємо за редагування, хоча зміна вашого формулювання на "типовий" просто ставить питання про популярність. Є причини, по яких кодування робиться так чи інакше, і хоча популярність може бути свідченням того, що техніка "хороша" (для деякого визначення "хорошого"), вона також може бути свідченням вантажопідйомки.
Роберт Харві

@RobertHarvey Я видалив "кращі" та "погані" у своєму останньому розділі. Я вважаю, що я прошу типового (можливо, навіть прихильного / заохоченого) підходу, коли у вас є об'єкт Networkкласу, який містить Particleоб'єкти та Contactоб'єкти. На основі цих базових знань я можу спробувати оцінити, чи відповідає це моїм конкретним потребам, які досі вивчаються / розвиваються в ході проекту.
AnInquitingMind

@RobertHarvey Я гадаю, що я досить новий, щоб писати проекти C ++ повністю з нуля, що я чудово розумію, що є "типовим" і "популярним". Сподіваюся, я набуду достатнього розуміння в якийсь момент, щоб змогти усвідомити, чому інша реалізація насправді краща, але поки що я просто хочу переконатися, що я не підходжу до цього цілком з головою!
AnInquitingMind

Відповіді:


17

У вашому запитанні є дві частини.

Перша частина - це організація файлів заголовків C ++ та вихідних файлів. Це вирішується за допомогою декларації вперед та розділення декларації класу (розміщення їх у файлі заголовка) та тіла методу (введення їх у вихідний файл). Крім того, в деяких випадках можна застосувати ідіому Pimpl ("вказівник на реалізацію") для вирішення більш важких випадків. Використовуйте покажчики розділеної власності ( shared_ptr), покажчики на одну власність ( unique_ptr) та невласники покажчиків (необроблений покажчик, тобто "зірочка") відповідно до кращих практик.

Друга частина - як моделювати об'єкти, які взаємопов'язані у вигляді графіка . Загальні графіки, які не є DAG (спрямовані ациклічні графіки), не мають природного способу вираження деревоподібної власності. Натомість, вузли та з'єднання - це всі метадані, які належать до одного об’єкта графа. У цьому випадку неможливо моделювати зв'язок вузол-зв’язок як сукупність. Вузли не "володіють" з'єднаннями; з'єднання не мають "власних" вузлів. Натомість вони є об'єднаннями, і обидва вузли та з'єднання "належать" графіку. Графік надає методи запитів та маніпуляцій, які працюють на вузлах та з'єднаннях.


Дякуємо за відповідь! Насправді у мене є мережевий клас, який буде мати N частинок і Nc контактів. Але я хотів мати змогу мати такі функції, як particles[0].getContacts()- ви в своєму останньому абзаці ви припускаєте, що я не повинен мати таких функцій у Particleкласі, або що поточна структура нормальна, оскільки вони споріднені / пов'язані через Network? Чи є в цьому випадку краща "структура" асоціації в C ++?
AnInquitingMind

1
Як правило, Мережа відповідає за знання про зв'язки між об'єктами. Наприклад, якщо ви використовуєте список суміжності, наприклад, частинка network.particle[p]повинна відповідати network.contacts[p]індексам своїх контактів. Інакше Мережа та Частинка якимось чином відслідковують однакову інформацію.
Марно

@Useless Так, саме тут я не знаю, як діяти. Отже, ви говорите, що Particleоб'єкт не повинен знати про свої контакти (тому я не повинен мати getContacts()функції члена), і що ця інформація повинна надходити тільки з Networkоб'єкта? Чи було б поганим дизайном класу C ++ для Particleоб'єкта мати такі знання (тобто мати кілька способів доступу до цієї інформації - або через Networkоб'єкт, або через Particleоб'єкт, що здається зручнішим)? Останнє, здається, має більше сенсу для мене, але, можливо, мені потрібно змінити свою точку зору на це.
AnInquitingMind

1
@PhysicsCodingEnthusiast: Проблема з тим, що Particleзнати що-небудь про Contacts або Networks, полягає в тому, що вона прив'язує вас до певного способу представлення цих відносин. Усі три класи, можливо, повинні погодитись. Якщо натомість Networkєдиний, хто знає чи піклується, це лише один клас, який потрібно змінити, якщо ви вирішите, що інше представлення краще.
cHao

@cHao Гаразд, це має сенс. Отже, Particleі Contactповинні бути повністю окремими, а асоціація між ними визначається Networkоб'єктом. Щоб бути повністю впевненим, це (мабуть) те, що мав на увазі @rwong, коли він / вона писав, "і вузли, і з'єднання" належать "графіку. Графік надає методи запитів і маніпуляцій, які працюють на вузлах і з'єднаннях". , правда?
AnInquitingMind

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: про це йшлося тут: stackoverflow.com/questions/3310737/… - "const" тут не дуже важливий, але передача об'єктів за посиланням (та скалярами за значенням) є стандартною ідіомою в C ++.
Док Браун

2
Багато хто з цих відповідей, схоже, з попередньої moveпарадигми
Калет

@Caleth: гаразд, щоб усі щасливці були щасливі, я змінив цю зовсім неважливу частину своєї відповіді.
Док Браун

1
@PhysicsCodingEnthusiast: немає, це в першу чергу про те , щоб particle1.getContacts()і particle2.getContacts()поставляти один і той же Contactоб'єкт , який представляє фізичний контакт між particle1і particle2, а не два різних об'єкта. Звичайно, можна спробувати спроектувати систему таким чином, що не має значення, чи є Contactодночасно два об'єкти, що представляють один і той же фізичний контакт. Це передбачає зробити Contactнепорушним, але ви впевнені, що саме цього ви хочете?
Док Браун

0

Так, те, що ви описуєте, є дуже прийнятним способом гарантувати, що кожен Contactекземпляр знаходиться у списку контактів Particle.


Дякуємо за відповідь. Я прочитав кілька пропозицій про те, що наявність пари взаємозалежних класів слід уникати (наприклад, у "C ++ Шаблони дизайну та цінові деривативи" від MS Joshi), але, мабуть, це не обов'язково правильно? З цікавості, чи є інший спосіб здійснення цього автоматичного оновлення без необхідності взаємозалежності?
AnInquitingMind

4
@PhysicsCodingEnthusiast: наявність взаємозалежних класів створює всілякі труднощі, і ви повинні намагатися їх уникати. Але іноді два класи настільки тісно пов'язані один з одним, що усунення взаємозалежності між ними викликає більше проблем, ніж сама взаємозалежність.
Барт ван Іґен Шенау

0

Те, що ви зробили, правильно.

Інший спосіб ... Якщо мета - переконатися, що кожен Contactє у списку, то ви можете:

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

Тоді не потрібно включати particle.hвcontact


Дякуємо за відповідь! Це здається корисним способом реалізації цього. Цікаво, що моє редагування на початкове запитання щодо Networkкласу, чи змінює це запропоновану структуру, чи все-таки буде те саме?
AnInquitingMind

Після того як ви оновили своє запитання, це змінює сферу застосування. ... Тепер ви запитуєте про архітектуру вашої програми, коли раніше мова йшла про технічну проблему.
Роберт Анджежук

0

Інший варіант, який ви можете розглянути, - це зробити конструктор Contact, який приймає посилання на частинку. Це дозволить Контакту додати себе до будь-якого контейнера, який реалізується addContact(Contact).

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.