เหตุใดวัตถุจึงผ่านการอ้างอิงถึง


31

เพื่อนร่วมงานอายุน้อยที่กำลังศึกษา OO ถามฉันว่าทำไมทุกสิ่งของถูกส่งผ่านโดยการอ้างอิงซึ่งเป็นสิ่งที่ตรงกันข้ามกับประเภทหรือโครงสร้างดั้งเดิม มันเป็นลักษณะทั่วไปของภาษาเช่น Java และ C #

ฉันไม่พบคำตอบที่ดีสำหรับเขา

แรงจูงใจในการตัดสินใจออกแบบนี้คืออะไร? นักพัฒนาภาษาเหล่านี้เบื่อที่จะต้องสร้างพอยน์เตอร์และพิมพ์ทุกครั้งหรือไม่?


2
คุณกำลังถามว่าเพราะเหตุใด Java และ C # ให้คุณส่งพารามิเตอร์ตามการอ้างอิงแทนด้วยค่าหรือโดยการอ้างอิงแทนการชี้?
เบิ

@Robert มีความแตกต่างระหว่าง "การอ้างอิงแทนที่จะเป็นตัวชี้" ในระดับสูงหรือไม่? คุณคิดว่าฉันควรเปลี่ยนชื่อเป็น 'ทำไมวัตถุอ้างอิงอยู่เสมอ? "
Gustavo Cardoso

การอ้างอิงเป็นตัวชี้
compman

2
@Anto: การอ้างอิง Java มีลักษณะเหมือนกับตัวชี้ C ที่ใช้อย่างถูกต้อง (ใช้อย่างถูกต้อง: ไม่ใช่การโยนแบบไม่ได้ตั้งค่าเป็นหน่วยความจำที่ไม่ถูกต้องไม่ได้ตั้งค่าตามตัวอักษร)
Zan Lynx

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

คำตอบ:


15

สาเหตุพื้นฐานมาจากสิ่งนี้:

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

ดังนั้นการอ้างอิง


32

คำตอบง่ายๆ:

การลดปริมาณการใช้หน่วยความจำ
และ
เวลา CPU ในการสร้างและทำสำเนาของวัตถุทุกชิ้นที่ผ่านไปที่ไหนสักแห่ง


ฉันเห็นด้วยกับคุณ แต่ฉันคิดว่ามีแรงบันดาลใจด้านสุนทรียภาพหรือการออกแบบ OO อยู่บ้าง
Gustavo Cardoso

9
@Gustavo Cardoso: "แรงบันดาลใจด้านสุนทรียภาพหรือการออกแบบ OO" ไม่มันเป็นแค่การเพิ่มประสิทธิภาพ
S.Lott

6
@ S.Lott: ไม่มันสมเหตุสมผลในการใช้ OO โดยการอ้างอิงเพราะในความหมายคุณไม่ต้องการทำสำเนาของวัตถุ คุณต้องการส่งผ่านวัตถุแทนที่จะเป็นสำเนา หากคุณผ่านคุณค่ามันจะทำลายอุปมาอุปมัย OO บ้างเพราะคุณมีโคลนของวัตถุเหล่านี้ถูกสร้างขึ้นทั่วสถานที่ที่ไม่สมเหตุสมผลในระดับที่สูงขึ้น
intuited

@Gustavo: ฉันคิดว่าเรากำลังโต้เถียงในประเด็นเดียวกัน คุณพูดถึงความหมายของ OOP และอ้างถึงคำอุปมาของ OOP เป็นเหตุผลเพิ่มเติมให้ฉันเอง มันดูเหมือนว่าฉันว่าผู้สร้างของ OOP ที่ทำให้มันเป็นวิธีที่พวกเขาจะ "ลดการใช้หน่วยความจำ" และ "บันทึกในเวลา CPU"
ทิม

14

ใน C ++ คุณมีสองตัวเลือกหลักคือ return by value หรือ return by pointer ลองดูที่แรก:

MyClass getNewObject() {
    MyClass newObj;
    return newObj;
}

สมมติว่าคอมไพเลอร์ของคุณไม่ฉลาดพอที่จะใช้การเพิ่มประสิทธิภาพค่าตอบแทนสิ่งที่เกิดขึ้นที่นี่คือ:

  • newObj ถูกสร้างเป็นวัตถุชั่วคราวและวางไว้บนสแต็กท้องถิ่น
  • สำเนาของ newObj ถูกสร้างและส่งคืน

เราทำสำเนาของวัตถุอย่างไม่มีจุดหมาย นี่เป็นการเสียเวลาในการประมวลผล

ลองดู return-by-pointer แทน:

MyClass* getNewObject() {
    MyClass newObj = new MyClass();
    return newObj;
}

เราได้กำจัดสำเนาที่ซ้ำซ้อน แต่ตอนนี้เราได้แนะนำปัญหาอื่น: เราได้สร้างวัตถุบนกองที่จะไม่ถูกทำลายโดยอัตโนมัติ เราต้องจัดการกับมันเอง:

MyClass someObj = getNewObject();
delete someObj;

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

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

ผู้สร้าง Java / C # ตระหนักว่าการส่งคืนออบเจ็กต์ตามการอ้างอิงเสมอเป็นทางออกที่ดีกว่าโดยเฉพาะอย่างยิ่งหากภาษานั้นรองรับ มันเชื่อมโยงกับคุณสมบัติอื่น ๆ อีกมากมายที่ภาษามีเช่นการเก็บขยะ ฯลฯ


Return-by-value แย่มาก แต่ Pass-by-value เลวร้ายยิ่งเมื่อพูดถึงวัตถุและฉันคิดว่านั่นเป็นปัญหาจริงที่พวกเขาพยายามหลีกเลี่ยง
Mason Wheeler

แน่นอนคุณมีจุดที่ถูกต้อง แต่ OO ออกแบบปัญหาที่ @Mason ชี้ว่าเป็นแรงจูงใจสุดท้ายของการเปลี่ยนแปลง ไม่มีความหมายที่จะรักษาความแตกต่างระหว่างการอ้างอิงและค่าเมื่อคุณต้องการใช้การอ้างอิง
Gustavo Cardoso

10

คำตอบอื่น ๆ อีกมากมายมีข้อมูลที่ดี ฉันต้องการเพิ่มจุดสำคัญอย่างหนึ่งเกี่ยวกับการโคลนที่ได้รับการแก้ไขเพียงบางส่วนเท่านั้น

การใช้การอ้างอิงนั้นฉลาด การทำสำเนาเป็นสิ่งที่อันตราย

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

สำเนาลึกเป็นปัญหาของพวกเขาเอง - คุณลึกซึ้งมากแค่ไหน? แน่นอนคุณไม่สามารถคัดลอกสิ่งที่คงที่ (รวมถึงClassวัตถุใด ๆ)

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


* ดูความคิดเห็น - โดยคำสั่ง "ไม่เคย" ฉันหมายถึงการโคลนอัตโนมัติที่โคลนทุกคุณสมบัติ Java ไม่ได้จัดเตรียมไว้ให้และอาจไม่ใช่ความคิดที่ดีสำหรับคุณในฐานะผู้ใช้ภาษาในการสร้างของคุณเองด้วยเหตุผลที่ระบุไว้ที่นี่ การโคลนเฉพาะเขตข้อมูลที่ไม่ใช่แบบชั่วคราวจะเป็นการเริ่มต้น แต่ถึงอย่างนั้นคุณจะต้องขยันหมั่นเพียรเกี่ยวกับการกำหนดtransientตามความเหมาะสม


ฉันมีปัญหาในการเข้าใจการกระโดดจากการคัดค้านที่ดีไปยังการโคลนนิ่งในเงื่อนไขบางประการไปยังข้อความที่ไม่จำเป็น และฉันได้พบสถานการณ์ที่แน่นอนซ้ำกันเป็นสิ่งที่จำเป็นที่ไม่มีฟังก์ชั่นแบบคงที่เกี่ยวข้องไม่มี IO หรือเปิดการเชื่อมต่ออาจจะมีปัญหา ... ฉันเข้าใจความเสี่ยงของการโคลน แต่ฉันไม่สามารถเห็นผ้าห่มไม่เคย
Inca

2
@Inca - คุณอาจเข้าใจฉันผิด การใช้งานโดยเจตนาcloneนั้นใช้ได้ โดย "willy-nilly" ฉันหมายถึงการคัดลอกคุณสมบัติทั้งหมดโดยไม่ต้องคำนึงถึงมัน - โดยไม่มีเจตนาเด็ดเดี่ยว ออกแบบภาษา Java cloneบังคับเจตนานี้โดยการกำหนดให้การดำเนินการที่ผู้ใช้สร้างของ
นิโคล

การใช้การอ้างอิงถึงวัตถุที่ไม่เปลี่ยนรูปนั้นเป็นสิ่งที่ฉลาด การสร้างค่าอย่างง่ายเช่นวันที่ไม่แน่นอนและจากนั้นสร้างการอ้างอิงหลายรายการถึงสิ่งเหล่านั้นไม่ใช่
วินไคลน์

@NickC: เหตุผลหลักที่ "การโคลนสิ่งต่าง ๆ โดยเจตนา" เป็นสิ่งที่อันตรายคือภาษา / กรอบเช่น Java และ. net ไม่มีวิธีการใด ๆ ในการระบุอย่างชัดเจนว่าการอ้างอิงนั้นห่อหุ้มรัฐที่ไม่แน่นอนทั้งสองอย่างหรือไม่ ถ้าเขตข้อมูลประกอบด้วยการอ้างอิงวัตถุที่ห่อหุ้มสถานะที่ไม่แน่นอน แต่ไม่ใช่ตัวตนการโคลนวัตถุนั้นต้องการให้วัตถุที่ถือครองสถานะนั้นถูกทำซ้ำและการอ้างอิงถึงสำเนาที่เก็บไว้ในเขตข้อมูลนั้น หากการอ้างอิงห่อหุ้มตัวตน แต่ไม่สามารถเปลี่ยนแปลงสถานะได้ฟิลด์ในสำเนาจะต้องอ้างถึงวัตถุเดียวกันเช่นเดียวกับในต้นฉบับ
supercat

ด้านสำเนาลึกเป็นจุดสำคัญ การคัดลอกวัตถุเป็นปัญหาเมื่อมีการอ้างอิงไปยังวัตถุอื่น ๆ โดยเฉพาะอย่างยิ่งหากกราฟวัตถุมีวัตถุที่ไม่แน่นอน
คาเลบ

4

มีการอ้างอิงวัตถุใน Java เสมอ พวกเขาไม่เคยผ่านรอบตัวเอง

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

อีกเหตุผลหนึ่งก็คือความแตกต่างนั้นจะต้องกระทำโดยการอ้างอิงไม่ใช่ตามตัวอักษร; วัตถุได้รับการปฏิบัติตามค่าอยู่ที่นั่นและมีประเภทคงที่ เป็นไปได้ที่จะไขปัญหานี้ใน C ++

นอกจากนี้ Java สามารถสลับการกำหนดค่าเริ่มต้น / คัดลอก / อะไรก็ได้ ใน C ++ มันเป็นสำเนาที่ลึกมากหรือน้อยในขณะที่ใน Java มันเป็นตัวชี้ / การกำหนดตัวชี้ / คัดลอกอย่างง่าย ๆ ด้วย.clone()ในกรณีที่คุณต้องการคัดลอก


บางครั้งมันก็น่าเกลียดจริงๆเมื่อคุณใช้ '(* วัตถุ) ->'
Gustavo Cardoso

1
เป็นที่น่าสังเกตว่า C ++ แยกความแตกต่างระหว่างพอยน์เตอร์การอ้างอิงและค่า SomeClass * เป็นตัวชี้ไปยังวัตถุ SomeClass & เป็นการอ้างอิงไปยังวัตถุ SomeClass เป็นประเภทค่า
Ant

ฉันได้ถาม @Rober เกี่ยวกับคำถามเริ่มต้นแล้ว แต่ฉันจะทำที่นี่ด้วย: ความแตกต่างระหว่าง * และ & บน C ++ เป็นเพียงแค่เทคโนโลยีระดับต่ำใช่ไหม? พวกเขาอยู่ในระดับสูงมีความหมายเหมือนกันหรือไม่
Gustavo Cardoso

3
@Gustavo Cardoso: ความแตกต่างคือความหมาย; ในระดับเทคนิคต่ำพวกมันเหมือนกันโดยทั่วไป ตัวชี้ชี้ไปที่วัตถุหรือเป็น NULL (ค่าที่ไม่ถูกต้องที่กำหนด) นอกจากconstว่าค่าของมันสามารถเปลี่ยนให้ชี้ไปที่วัตถุอื่น ๆ การอ้างอิงเป็นชื่ออื่นสำหรับวัตถุไม่สามารถเป็นค่า NULL และไม่สามารถทำซ้ำได้ โดยทั่วไปแล้วจะมีการใช้งานโดยการใช้ตัวชี้อย่างง่าย แต่นั่นเป็นรายละเอียดการใช้งาน
David Thornley

+1 สำหรับ "polymorphism ต้องทำโดยการอ้างอิง" นั่นเป็นรายละเอียดที่สำคัญอย่างไม่น่าเชื่อที่คำตอบอื่น ๆ ส่วนใหญ่ไม่สนใจ
Doval

4

ข้อความสั่งเริ่มต้นของคุณเกี่ยวกับวัตถุ C # ที่ส่งผ่านโดยการอ้างอิงไม่ถูกต้อง ใน C # วัตถุเป็นประเภทการอ้างอิง แต่โดยค่าเริ่มต้นพวกเขาจะถูกส่งผ่านตามค่าเช่นเดียวกับประเภทค่า ในกรณีของประเภทการอ้างอิง "ค่า" ที่ถูกคัดลอกเป็นพารามิเตอร์วิธีการส่งผ่านค่าเป็นการอ้างอิงตัวเองดังนั้นการเปลี่ยนแปลงคุณสมบัติภายในวิธีการจะมีผลนอกขอบเขตวิธีการ

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


3

คำตอบที่รวดเร็ว

นักออกแบบของ Java และภาษาที่เหมือนกันต้องการใช้แนวคิด "ทุกอย่างเป็นวัตถุ" และการส่งข้อมูลเนื่องจากการอ้างอิงนั้นรวดเร็วมากและไม่ได้ใช้หน่วยความจำมากนัก

ความคิดเห็นที่น่าเบื่อเพิ่มเติมเพิ่มเติม

Altougth ภาษาเหล่านั้นใช้การอ้างอิงวัตถุ (Java, Delphi, C #, VB.NET, Vala, Scala, PHP) ความจริงก็คือการอ้างอิงวัตถุเป็นตัวชี้ไปยังวัตถุที่ปลอมตัว ค่า Null, การจัดสรรหน่วยความจำ, สำเนาของการอ้างอิงโดยไม่คัดลอกข้อมูลทั้งหมดของวัตถุทั้งหมดนี้เป็นตัวชี้วัตถุไม่ใช่วัตถุธรรมดา !!!

ใน Object Pascal (ไม่ใช่ Delphi), anc C ++ (ไม่ใช่ Java, ไม่ใช่ C #), อ็อบเจ็กต์สามารถถูกประกาศเป็นตัวแปรที่ถูกจัดสรรแบบคงที่, และยังมีตัวแปรที่ถูกจัดสรรแบบไดนามิก, ผ่านการใช้ตัวชี้ ("การอ้างอิงวัตถุ" โดยไม่มี ") ไวยากรณ์น้ำตาล ") แต่ละกรณีใช้ไวยากรณ์ที่แน่นอนและไม่มีทางสับสนใน Java "และเพื่อน" ในภาษาเหล่านั้นวัตถุสามารถถูกส่งผ่านเป็นค่าหรือเป็นข้อมูลอ้างอิง

โปรแกรมเมอร์รู้เมื่อจำเป็นต้องใช้ไวยากรณ์ของตัวชี้และเมื่อไม่จำเป็น แต่ในภาษาจาวาและภาษาที่เหมือนกันสิ่งนี้ทำให้เกิดความสับสน

ก่อนที่ Java จะมีอยู่หรือกลายเป็นกระแสหลักโปรแกรมเมอร์จำนวนมากเรียนรู้ OO ใน C ++ โดยไม่มีตัวชี้ผ่านค่าหรือตามการอ้างอิงเมื่อจำเป็น เมื่อเปลี่ยนจากการเรียนรู้เป็นแอพธุรกิจแล้วพวกเขามักใช้พอยน์เตอร์พอยน์เตอร์ ห้องสมุด QT เป็นตัวอย่างที่ดี

เมื่อฉันเรียนรู้ Java ฉันพยายามติดตามทุกอย่างที่เป็นแนวคิดของวัตถุ แต่สับสนในการเขียนโค้ด ในที่สุดฉันก็บอกว่า "โอเคนี่คือวัตถุที่ถูกจัดสรรแบบไดนามิกด้วยตัวชี้ที่มีรูปแบบของวัตถุที่ถูกจัดสรรแบบคงที่" และไม่มีปัญหากับรหัสอีกแล้ว


3

Java และ C # ทำการควบคุมหน่วยความจำระดับต่ำจากคุณ "ฮีป" ที่วัตถุที่คุณสร้างอาศัยอยู่ในชีวิตของมันเอง ตัวอย่างเช่นตัวรวบรวมขยะจะเก็บรวบรวมวัตถุเมื่อใดก็ตามที่ต้องการ

เนื่องจากมีเลเยอร์ทางอ้อมแยกกันระหว่างโปรแกรมของคุณกับ "ฮีป" ทั้งสองวิธีในการอ้างถึงวัตถุตามค่าและตามตัวชี้ (เช่นใน C ++) แยกไม่ออก : คุณมักอ้างถึงวัตถุ "โดยตัวชี้" เพื่อ ที่ไหนสักแห่งในกอง นั่นเป็นเหตุผลที่วิธีการออกแบบดังกล่าวทำให้ความหมายเริ่มต้นของการมอบหมายผ่านโดยอ้างอิง Java, C #, Ruby, และอื่น ๆ

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


2

ฉันนึกถึงเหตุผลสองสามข้อ:

  • การทำสำเนาแบบดั้งเดิมนั้นเป็นเรื่องเล็กน้อยโดยปกติจะแปลเป็นหนึ่งคำสั่งในเครื่อง

  • การคัดลอกวัตถุนั้นไม่สำคัญวัตถุสามารถมีสมาชิกที่เป็นวัตถุได้ การคัดลอกวัตถุมีราคาแพงในเวลาและหน่วยความจำของ CPU การคัดลอกวัตถุมีหลายวิธีขึ้นอยู่กับบริบท

  • การส่งผ่านวัตถุโดยการอ้างอิงนั้นมีราคาถูกและยังมีประโยชน์เมื่อคุณต้องการแบ่งปัน / อัปเดตข้อมูลวัตถุระหว่างไคลเอนต์หลายตัวของวัตถุ

  • โครงสร้างข้อมูลที่ซับซ้อน (โดยเฉพาะโครงสร้างแบบเรียกซ้ำ) จำเป็นต้องมีพอยน์เตอร์ การส่งผ่านวัตถุโดยการอ้างอิงเป็นเพียงวิธีที่ปลอดภัยกว่าในการส่งพอยน์เตอร์


1

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


มันสามารถทำสำเนาตื้น ๆ และเก็บค่าจริงและพอยน์เตอร์ไปยังวัตถุอื่นได้หรือไม่?
Gustavo Cardoso

#Gustavo Cardoso แล้วคุณสามารถแก้ไขวัตถุอื่น ๆ ผ่านวัตถุนี้เป็นสิ่งที่คุณคาดหวังจากวัตถุที่ไม่ผ่านการอ้างอิง?
David

0

เนื่องจาก Java ได้รับการออกแบบให้เป็น C ++ ที่ดีกว่าและ C # ได้รับการออกแบบให้เป็น Java ที่ดีขึ้นและผู้พัฒนาภาษาเหล่านี้ก็เบื่อกับโมเดลวัตถุ C ++ ที่ใช้งานไม่ได้ซึ่งเป็นชนิดของค่า

สองในสามหลักการพื้นฐานของการเขียนโปรแกรมเชิงวัตถุคือการสืบทอดและความหลากหลายและการปฏิบัติต่อวัตถุเป็นชนิดของค่าแทนที่จะเป็นประเภทการอ้างอิงทำให้เกิดความเสียหายทั้งสองอย่าง เมื่อคุณส่งวัตถุไปยังฟังก์ชั่นเป็นพารามิเตอร์คอมไพเลอร์จำเป็นต้องรู้จำนวนไบต์ที่จะผ่าน เมื่อวัตถุของคุณเป็นประเภทอ้างอิงคำตอบนั้นง่าย: ขนาดของตัวชี้เหมือนกับวัตถุทั้งหมด แต่เมื่อวัตถุของคุณเป็นประเภทค่ามันจะต้องผ่านขนาดที่แท้จริงของค่า เนื่องจากคลาสที่ได้รับสามารถเพิ่มฟิลด์ใหม่ได้นั่นหมายถึง sizeof (ที่ได้รับ)! = sizeof (ฐาน) และ polymorphism จะออกไปนอกหน้าต่าง

นี่คือโปรแกรม C ++ ที่แสดงให้เห็นถึงปัญหา:

#include <iostream> 
class Parent 
{ 
public: 
   int a;
   int b;
   int c;
   Parent(int ia, int ib, int ic) { 
      a = ia; b = ib; c = ic;
   };
   virtual void doSomething(void) { 
      std::cout << "Parent doSomething" << std::endl;
   }
};

class Child : public Parent {
public:
   int d;
   int e;
   Child(int id, int ie) : Parent(1,2,3) { 
      d = id; e = ie;
   };
   virtual void doSomething(void) {
      std::cout << "Child doSomething : D = " << d << std::endl;
   }
};

void foo(Parent a) {
   a.doSomething();
}

int main(void)
{
   Child c(4, 5);
   foo(c);
   return 0;
}

ผลลัพธ์ของโปรแกรมนี้ไม่ใช่สิ่งที่มันจะเป็นสำหรับโปรแกรมที่เทียบเท่าในภาษา OO ใด ๆ เพราะคุณไม่สามารถผ่านวัตถุที่ได้มาโดยค่าไปยังฟังก์ชั่นที่คาดหวังวัตถุฐานดังนั้นคอมไพเลอร์สร้างตัวสร้างสำเนาที่ซ่อนอยู่และผ่านสำเนาของส่วนผู้ปกครองของวัตถุเด็กแทนที่จะส่งวัตถุเด็กเช่นที่คุณบอกให้ทำ gotchas ความหมายที่ซ่อนอยู่เช่นนี้เป็นสาเหตุที่การหลีกเลี่ยงวัตถุตามค่าควรหลีกเลี่ยงใน C ++ และไม่สามารถทำได้ในเกือบทุกภาษา OO อื่น ๆ


จุดที่ดีมาก อย่างไรก็ตามฉันมุ่งเน้นไปที่ปัญหาการคืนเนื่องจากการทำงานกับพวกเขานั้นใช้ความพยายามบ้าง โปรแกรมนี้สามารถแก้ไขได้ด้วยการเพิ่มเครื่องหมายแอมเปอร์แซนด์เดียว: void foo (Parent & a)
Ant

OO ทำงานไม่ถูกต้องหากไม่มีพอยน์เตอร์
Gustavo Cardoso

-1.000000000000
P Shved

5
สิ่งสำคัญคือการจำจาวาคือการผ่านตามตัวอักษร (มันผ่านการอ้างอิงวัตถุตามค่าในขณะที่ดั้งเดิมจะถูกส่งผ่านโดยค่า)
นิโคล

3
@Pavel Shved - "สองดีกว่าหนึ่ง!" หรือพูดอีกอย่างก็คือคุณควรใช้เชือกมากขึ้น
นิโคล

0

เพราะจะไม่มีความแตกต่างเป็นอย่างอื่น

ในการเขียนโปรแกรม OO คุณอาจสร้างDerivedคลาสที่มีขนาดใหญ่ขึ้นจากคลาสBaseหนึ่งและส่งต่อไปยังฟังก์ชันที่คาดหวังคลาสBaseหนึ่ง ใช่มั้ย

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

ขณะนี้มีข้อมูลหนึ่งชิ้นที่กำหนดไว้อย่างดีในคอมพิวเตอร์: ที่อยู่ของเซลล์หน่วยความจำซึ่งมักแสดงเป็น "คำ" หนึ่งหรือสองคำ มันสามารถมองเห็นได้ทั้งตัวชี้หรือการอ้างอิงในภาษาการเขียนโปรแกรม

ดังนั้นเพื่อที่จะผ่านวัตถุที่มีความยาวโดยพลการสิ่งที่ง่ายที่สุดที่จะทำคือส่งตัวชี้ / อ้างอิงไปยังวัตถุนี้

นี่เป็นข้อ จำกัด ทางเทคนิคของการเขียนโปรแกรม OO

แต่เนื่องจากประเภทขนาดใหญ่โดยทั่วไปคุณต้องการส่งผ่านการอ้างอิงอยู่แล้วเพื่อหลีกเลี่ยงการคัดลอกโดยทั่วไปจะไม่ถือว่าเป็นการระเบิดครั้งใหญ่ :)

มีความสำคัญอย่างหนึ่งที่สำคัญแม้ว่าใน Java หรือ C # เมื่อผ่านวัตถุไปยังวิธีการคุณไม่ทราบว่าวัตถุของคุณจะถูกแก้ไขโดยวิธีการหรือไม่ มันทำให้การแก้จุดบกพร่อง / ขนานที่ยากขึ้นและนี่คือปัญหาการทำงานภาษาและการอ้างอิงโปร่งใสกำลังพยายามที่จะอยู่ -> คัดลอกไม่ได้เลวร้าย (เมื่อมันทำให้รู้สึก)


-1

คำตอบอยู่ในชื่อ (เกือบจะอย่างไรก็ตาม) การอ้างอิง (เช่นที่อยู่) หมายถึงสิ่งอื่นค่าคือสำเนาของสิ่งอื่น ฉันแน่ใจว่าบางคนอาจพูดถึงบางสิ่งบางอย่างเกี่ยวกับผลกระทบต่อไปนี้ แต่อาจมีบางสถานการณ์ที่สถานการณ์หนึ่งและไม่เหมาะสม มันคือทั้งหมดที่เกี่ยวกับการจัดการหน่วยความจำหน่วยความจำหน่วยความจำ ...... MEMORY! : D


-2

เอาล่ะฉันไม่ได้บอกว่านี่คือสาเหตุที่วัตถุเป็นประเภทอ้างอิงหรือผ่านการอ้างอิง แต่ฉันสามารถให้คุณตัวอย่างว่าทำไมนี่เป็นความคิดที่ดีมากในระยะยาว

ถ้าฉันไม่ผิดเมื่อคุณรับคลาสใน C ++ วิธีการและคุณสมบัติทั้งหมดของคลาสนั้นจะถูกคัดลอกลงในคลาสลูก มันจะเหมือนกับการเขียนเนื้อหาของชั้นเรียนนั้นอีกครั้งในชั้นเรียนของเด็ก

ดังนั้นนี่หมายความว่าขนาดรวมของข้อมูลในคลาสลูกของคุณคือการรวมกันของสิ่งต่าง ๆ ในคลาสแม่และคลาสที่ได้รับ

EG: #include

class Top 
{   
    int arrTop[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

class Middle : Top 
{   
    int arrMiddle[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

class Bottom : Middle
{   
    int arrBottom[20] = {1,2,3,4,5,6,7,8,9,9,8,7,6,5,4,3,2,1};
};  

int main()
{   
    using namespace std;

    int arr[20];
    cout << "Size of array of 20 ints: " << sizeof(arr) << endl;

    Top top;
    Middle middle;
    Bottom bottom;

    cout << "Size of Top Class: " << sizeof(top) << endl;
    cout << "Size of middle Class: " << sizeof(middle) << endl;
    cout << "Size of bottom Class: " << sizeof(bottom) << endl;

}   

สิ่งไหนที่จะแสดง:

Size of array of 20 ints: 80
Size of Top Class: 80
Size of middle Class: 160
Size of bottom Class: 240

ซึ่งหมายความว่าหากคุณมีลำดับชั้นขนาดใหญ่ของหลายคลาสขนาดรวมของวัตถุตามที่ประกาศไว้ที่นี่จะเป็นการรวมกันของคลาสเหล่านั้นทั้งหมด เห็นได้ชัดว่าวัตถุเหล่านี้มีขนาดใหญ่มากในหลายกรณี

ฉันเชื่อว่าวิธีแก้ปัญหาคือการสร้างมันบนฮีปและใช้พอยน์เตอร์ ซึ่งหมายความว่าขนาดของวัตถุของคลาสที่มีผู้ปกครองหลายคนจะสามารถจัดการได้ในแง่หนึ่ง

นี่คือเหตุผลที่ใช้การอ้างอิงจะเป็นวิธีที่ดีกว่าสำหรับการทำเช่นนี้


1
สิ่งนี้ดูเหมือนจะไม่นำเสนออะไรมากมายเกินกว่าที่ทำคะแนนและอธิบายไว้ในคำตอบก่อนหน้า 13 ข้อ
gnat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.