วิธีแก้ปัญหาการพึ่งพาซึ่งกันและกันในรหัส 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 particles ( std::vector<Particle> particles) และ Nc contact ( std::vector<Contact> contacts) แต่ฉันต้องการที่จะมีฟังก์ชั่นเช่นparticles[0].getContacts()- มันไม่เป็นไรที่จะมีฟังก์ชั่นดังกล่าวในParticleชั้นเรียนในกรณีนี้หรือมีโครงสร้าง "เชื่อมโยง" ที่ดีขึ้นใน C ++ สำหรับวัตถุประสงค์นี้ (ของสองคลาสที่เกี่ยวข้องที่ใช้ในชั้นเรียนอื่น) .


ฉันอาจต้องเปลี่ยนมุมมองที่นี่ในวิธีที่ฉันเข้าใกล้นี้ เนื่องจากทั้งสองคลาสเชื่อมต่อกันด้วยNetworkอ็อบเจกต์คลาสเป็นรหัสองค์กร / คลาสทั่วไปที่มีข้อมูลการเชื่อมต่อที่ควบคุมโดยNetworkวัตถุทั้งหมด (ในที่วัตถุ Particle ไม่ควรรับรู้รายชื่อผู้ติดต่อและดังนั้นจึงไม่ควรมีgetContacts()สมาชิก ฟังก์ชั่น) จากนั้นเพื่อที่จะทราบว่ามีอนุภาคใดบ้างที่มีการสัมผัสฉันต้องได้รับข้อมูลนั้นผ่านNetworkวัตถุ (เช่นใช้network.getContacts(Particle particle))

มันจะเป็นเรื่องธรรมดาน้อยลงหรือไม่ (อาจท้อใจ) การออกแบบคลาส C ++ สำหรับวัตถุอนุภาคที่มีความรู้เช่นกัน (เช่นมีหลายวิธีในการเข้าถึงข้อมูลนั้น - ผ่านทั้งวัตถุเครือข่ายหรือวัตถุอนุภาคซึ่งดูเหมือนว่าสะดวกกว่า )?


4
นี่คือการพูดคุยจาก cppcon 2017 - The Three Layers of Headers: youtu.be/su9ittf-ozk
Robert Andrzejuk

3
คำถามที่มีคำเช่น "ดีที่สุด" "ดีกว่า" และ "ยอมรับ" จะไม่สามารถตอบได้เว้นแต่คุณจะระบุเกณฑ์การประเมินเฉพาะของคุณ
Robert Harvey

ขอบคุณสำหรับการแก้ไขแม้ว่าการเปลี่ยนข้อความของคุณเป็น "ทั่วไป" เพียงแค่ทำให้มันเป็นคำถามของความนิยม มีเหตุผลว่าทำไมการเข้ารหัสจะทำได้ไม่ทางใดก็ทางหนึ่งและในขณะที่ความนิยมสามารถบ่งชี้ว่าเทคนิคคือ "ดี" (สำหรับคำจำกัดความของ "ดี") มันก็สามารถบ่งบอกถึงการปลูกฝังสินค้า
Robert Harvey

@RobertHarvey ฉันลบ "ดีกว่า" และ "ไม่ดี" ในส่วนสุดท้ายของฉัน ฉันคิดว่าฉันกำลังขอวิธีการทั่วไป (บางทีอาจจะชอบ / สนับสนุน) เมื่อคุณมีNetworkวัตถุคลาสที่มีParticleวัตถุและContactวัตถุ ด้วยความรู้พื้นฐานนั้นฉันสามารถลองประเมินว่าเหมาะสมกับความต้องการเฉพาะของฉันหรือไม่ซึ่งยังคงถูกสำรวจ / พัฒนาเมื่อฉันไปตามในโครงการ
AnInquiringMind

@ RobertHarvey ฉันคิดว่าฉันใหม่พอที่จะเขียนโครงการ C ++ อย่างสมบูรณ์ตั้งแต่เริ่มต้นว่าฉันสบายดีกับการเรียนรู้ว่า "ทั่วไป" และ "ยอดนิยม" คืออะไร หวังว่าฉันจะได้รับความเข้าใจที่เพียงพอในบางจุดเพื่อให้สามารถตระหนักได้ว่าทำไมการใช้งานอื่นถึงดีกว่าจริง ๆ แต่สำหรับตอนนี้ฉันแค่ต้องการให้แน่ใจว่าฉันไม่ได้เข้าใกล้สิ่งนี้ในทางที่สมบูรณ์แบบ!
AnInquiringMind

คำตอบ:


17

คำถามของคุณมีสองส่วน

ส่วนแรกคือการจัดระเบียบของไฟล์ส่วนหัว C ++ และไฟล์ต้นฉบับ นี่คือการแก้ไขโดยใช้การประกาศไปข้างหน้าและการแยกของการประกาศคลาส (วางไว้ในไฟล์ส่วนหัว) และวิธีร่างกาย (วางไว้ในไฟล์ต้นฉบับ) นอกจากนี้ในบางกรณีเราสามารถใช้Pimpl idiom ("ตัวชี้ไปยังการใช้งาน")เพื่อแก้ปัญหาที่ยากขึ้น ใช้ตัวชี้ความเป็นเจ้าของร่วม ( shared_ptr), ตัวชี้ความเป็นเจ้าของเดียว ( unique_ptr) และตัวชี้ที่ไม่ได้เป็นเจ้าของ (ตัวชี้แบบดิบเช่น "เครื่องหมายดอกจัน") ตามแนวทางปฏิบัติที่ดีที่สุด

ส่วนที่สองคือวิธีการสร้างแบบจำลองวัตถุที่มีการเชื่อมโยงกันในรูปแบบของกราฟ กราฟทั่วไปที่ไม่ใช่ DAG (กราฟเส้นตรงกำกับ) ไม่มีวิธีธรรมชาติในการแสดงความเป็นเจ้าของต้นไม้ แต่โหนดและการเชื่อมต่อนั้นเป็นข้อมูลเมตาทั้งหมดที่เป็นของวัตถุกราฟเดียว ในกรณีนี้มันเป็นไปไม่ได้ที่จะสร้างแบบจำลองความสัมพันธ์การเชื่อมต่อโหนดเป็นการรวม โหนดไม่ได้เชื่อมต่อ "เจ้าของ" การเชื่อมต่อไม่ได้เป็น "เจ้าของ" โหนด แต่มันคือการเชื่อมโยงและทั้งโหนดและการเชื่อมต่อนั้นเป็นกราฟของ "เจ้าของ" กราฟแสดงวิธีการสืบค้นและการจัดการที่ทำงานบนโหนดและการเชื่อมต่อ


ขอบคุณสำหรับคำตอบ! จริง ๆ แล้วฉันมีคลาสเครือข่ายที่จะมีอนุภาค N และติดต่อ Nc แต่ฉันต้องการที่จะมีฟังก์ชั่นเช่นparticles[0].getContacts()- คุณแนะนำในย่อหน้าสุดท้ายของคุณว่าฉันไม่ควรมีฟังก์ชั่นดังกล่าวในParticleชั้นเรียนหรือว่าโครงสร้างปัจจุบันไม่เป็นไรเพราะพวกมันเกี่ยวข้องกัน / เกี่ยวข้องกันอย่างแท้จริงNetwork? มี "โครงสร้าง" การเชื่อมโยงที่ดีขึ้นใน C ++ ในกรณีนี้หรือไม่
AnInquiringMind

1
โดยทั่วไปแล้วเครือข่ายมีหน้าที่รับผิดชอบในการทราบเกี่ยวกับความสัมพันธ์ระหว่างวัตถุ หากคุณใช้รายการ adjacency ตัวอย่างเช่นอนุภาค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" ไม่สำคัญจริงๆที่นี่ แต่การส่งวัตถุโดยอ้างอิง (และ scalars ตามค่า) เป็นสำนวนมาตรฐานใน C ++
Doc Brown

2
ดูเหมือนว่าคำตอบหลายคำตอบนั้นมาจากmoveกระบวนทัศน์ก่อน
Caleth

@Caleth: ตกลงเพื่อให้ nitpickers ทั้งหมดมีความสุขฉันเปลี่ยนส่วนนี้ค่อนข้างสำคัญของคำตอบของฉัน
Doc Brown

1
@PhysicsCodingEnthusiast: ไม่มีนี้เป็นสำคัญที่สุดเกี่ยวกับการทำparticle1.getContacts()และparticle2.getContacts()การส่งมอบเดียวกันContactวัตถุที่เป็นตัวแทนของการติดต่อทางกายภาพระหว่างparticle1และparticle2และไม่สองวัตถุที่แตกต่าง แน่นอนว่าเราสามารถลองออกแบบระบบในลักษณะที่ไม่สำคัญว่าจะมีContactวัตถุสองชิ้นในเวลาเดียวกันซึ่งแสดงถึงการสัมผัสทางกายภาพเดียวกัน สิ่งนี้จะเกี่ยวข้องกับการทำให้Contactไม่เปลี่ยนรูป แต่คุณแน่ใจหรือไม่ว่านี่คือสิ่งที่คุณต้องการ
Doc Brown

0

ใช่สิ่งที่คุณอธิบายเป็นวิธีที่ได้รับการยอมรับมากในการสร้างความมั่นใจว่าทุกตัวอย่างอยู่ในรายชื่อผู้ติดต่อของที่ContactParticle


ขอบคุณสำหรับคำตอบ ฉันได้อ่านคำแนะนำบางอย่างที่ควรหลีกเลี่ยงการเรียนแบบพึ่งพาซึ่งกันและกัน (เช่นใน "รูปแบบการออกแบบ C + + และอนุพันธ์ราคา" โดย MS Joshi) แต่เห็นได้ชัดว่าไม่ถูกต้องหรือไม่ จากความอยากรู้อาจมีวิธีอื่นในการใช้การอัปเดตอัตโนมัตินี้โดยไม่จำเป็นต้องพึ่งพาซึ่งกันและกันหรือไม่
AnInquiringMind

4
@PhysicsCodingEnthusiast: การมีคลาสที่พึ่งพาซึ่งกันและกันก่อให้เกิดปัญหาทุกชนิดและคุณควรพยายามหลีกเลี่ยง แต่บางครั้งสองชั้นมีความสัมพันธ์กันอย่างใกล้ชิดซึ่งการเอาการพึ่งพาซึ่งกันและกันระหว่างพวกเขาทำให้เกิดปัญหามากกว่าการพึ่งพาซึ่งกันและกัน
Bart van Ingen Schenau

0

สิ่งที่คุณทำถูกต้อง

อีกวิธีหนึ่ง ... หากเป้าหมายคือเพื่อให้แน่ใจว่าทุกอย่างContactอยู่ในรายการคุณสามารถ:

  • การสร้างบล็อกของContact(ผู้สร้างส่วนตัว)
  • ไปข้างหน้าประกาศParticleคลาส
  • ทำให้Particleระดับของเพื่อนContact,
  • ในการParticleสร้างวิธีการโรงงานซึ่งสร้างContact

แล้วคุณจะได้ไม่ต้องรวมถึงparticle.hในcontact


ขอบคุณสำหรับคำตอบ! ดูเหมือนว่าเป็นวิธีที่มีประโยชน์ในการดำเนินการนี้ เพียงแค่สงสัยว่าด้วยการแก้ไขของฉันกับคำถามเริ่มต้นเกี่ยวกับNetworkชั้นเรียนที่ไม่เปลี่ยนโครงสร้างที่แนะนำหรือมันจะยังคงเหมือนเดิม?
AnInquiringMind

หลังจากคุณอัปเดตคำถามของคุณแล้วมันกำลังเปลี่ยนขอบเขต ... ตอนนี้คุณกำลังถามถึงสถาปัตยกรรมของแอปพลิเคชันของคุณเมื่อก่อนหน้านี้มันเกี่ยวกับปัญหาทางเทคนิค
Robert Andrzejuk

0

ตัวเลือกอื่นที่คุณอาจพิจารณาคือการสร้างคอนสตรัคเตอร์ที่ยอมรับเทมเพลตอ้างอิงอนุภาค นี้จะช่วยให้การติดต่อเพื่อเพิ่มตัวเองไปยังภาชนะใด ๆ addContact(Contact)ที่นำไปปฏิบัติ

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.