คลาสพื้นฐานที่เป็นนามธรรมและการสร้างสำเนากฎของหัวแม่มือ


10

บ่อยครั้งที่เป็นความคิดที่ดีที่จะมีคลาสฐานนามธรรมเพื่อแยกอินเทอร์เฟซของวัตถุ

ปัญหาคือการสร้างการคัดลอก IMHO นั้นค่อนข้างเสียหายโดยค่าเริ่มต้นใน C ++ โดยที่ตัวสร้างสำเนาจะถูกสร้างขึ้นตามค่าเริ่มต้น

ดังนั้น gotchas คืออะไรเมื่อคุณมีคลาสฐานนามธรรมและพอยน์เตอร์ดิบในคลาสที่ได้รับ

class IAbstract
{
    ~IAbstract() = 0;
}

class Derived : public IAbstract
{
    char *theProblem;
    ...
}

IAbstract *a1 = new Derived();
IAbstract a2 = *a1;//???

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

มีกฎสามข้อในคลาสฐานนามธรรมหรือไม่?


1
ใช้การอ้างอิงแทนพอยน์เตอร์ :)
tp1

@ tp1: หรือตัวชี้อัจฉริยะบางตัวเป็นอย่างน้อย
Benjamin Bannier

1
บางครั้งคุณต้องทำงานกับรหัสที่มีอยู่ ... คุณไม่สามารถเปลี่ยนแปลงทุกอย่างในทันที
Coder

ทำไมคุณถึงคิดว่าตัวสร้างสำเนาเริ่มต้นเสีย?
BЈовић

2
@Coder: คู่มือสไตล์ Google เป็นกองขยะและเป็นสิ่งที่แย่มากสำหรับการพัฒนา C ++
DeadMG

คำตอบ:


6

การคัดลอกสิ่งก่อสร้างในคลาสนามธรรมควรทำแบบเป็นส่วนตัวในกรณีส่วนใหญ่รวมถึงตัวดำเนินการที่ได้รับมอบหมาย

คลาสนามธรรมโดยนิยามให้เป็นประเภท polymorphic ดังนั้นคุณจึงไม่ทราบว่าหน่วยความจำของคุณใช้งานอินสแตนซ์จำนวนเท่าใดจึงไม่สามารถคัดลอกหรือกำหนดได้อย่างปลอดภัย ในทางปฏิบัติคุณมีความเสี่ยงในการแบ่งส่วน: /programming/274626/what-is-the-the-slicing-problem-in-c

ชนิด Polymorphic ใน C ++ จะต้องไม่ถูกจัดการตามค่า คุณจัดการพวกเขาโดยการอ้างอิงหรือโดยตัวชี้ (หรือตัวชี้สมาร์ทใด ๆ )

นี่คือเหตุผลที่ Java ทำให้วัตถุสามารถจัดการได้โดยการอ้างอิงเท่านั้นและทำไม C # และ D จึงมีการแยกระหว่างคลาสและ structs (อันแรกคือ polymorphic และประเภทการอ้างอิงส่วนที่สองไม่ใช่ polymorphic และประเภทค่า)


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

3
Meh มันไม่ใช่ปัญหาใครที่รู้ว่า C ++ จะไม่ทำผิดพลาด ที่สำคัญไม่มีเหตุผลเลยที่จะจัดการ polymorphic types ตามมูลค่าถ้าคุณรู้ว่าคุณกำลังทำอะไรอยู่ คุณกำลังเริ่มต้นปฏิกิริยาสะบัดหัวเข่าทั้งหมดเกี่ยวกับความเป็นไปได้ทางเทคนิคที่บางเฉียบ ตัวชี้ NULL เป็นปัญหาใหญ่
DeadMG

8

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


และจะเกิดอะไรขึ้นถ้ามีบทคัดย่อ -> Derived1 -> ลำดับชั้น Derived2 และ Derived2 ถูกกำหนดให้กับ Derived1 มุมมืดของภาษายังคงทำให้ฉันประหลาดใจ
Coder

@Coder: ที่มักจะถูกมองว่าเป็น PEBKAC อย่างไรก็ตามเพิ่มเติมโดยตรงคุณก็สามารถประกาศส่วนตัวในoperator=(const Derived2&) Derived1
DeadMG

ตกลงบางทีนี่อาจจะไม่ใช่ปัญหา ฉันแค่ต้องการให้แน่ใจว่าชั้นเรียนปลอดภัยจากการถูกละเมิด
Coder

2
และเพื่อที่คุณจะได้รับเหรียญ
วิศวกรโลก

2
@Coder: การละเมิดโดยใคร สิ่งที่ดีที่สุดที่คุณสามารถทำได้คือทำให้การเขียนรหัสถูกต้องง่าย ไม่มีประโยชน์ที่จะพยายามป้องกันโปรแกรมเมอร์ที่ไม่รู้จัก C ++
kevin cline

2

โดยการทำ ctor และการมอบหมายส่วนตัว (หรือโดยการประกาศเป็น = ลบใน C ++ 11) คุณปิดการคัดลอก

จุดนี่คือที่ที่คุณต้องทำ ที่จะอยู่กับรหัสของคุณ IAbstract ไม่ใช่ปัญหา (โปรดทราบว่าการทำสิ่งที่คุณทำคุณกำหนด*a1 IAbstractsubobject ให้กับ a2 สูญเสียการอ้างอิงใด ๆ ไปที่การDerivedกำหนดค่าไม่ใช่ polymorphic)

Derived::theproblemปัญหาที่มาพร้อมกับ คัดลอกมาลงในเดือนพฤษภาคมอีกในความเป็นจริงร่วมกัน*theproblemข้อมูลที่อาจจะไม่ได้ออกแบบให้ใช้ร่วมกัน (มีสองกรณีที่อาจเรียกdelete theproblemใน destructor ของพวกเขา)

หากเป็นกรณีนี้Derivedจะต้องไม่สามารถคัดลอกได้และไม่สามารถกำหนดได้ แน่นอนถ้าคุณทำสำเนาส่วนตัวIAbstractเนื่องจากสำเนาเริ่มต้นสำหรับDerivedความต้องการนั้นDerivedจะไม่สามารถคัดลอกได้ แต่ถ้าคุณกำหนดของคุณเองDerived::Derived(const Derived&)โดยไม่เรียกIAbtractคัดลอกคุณยังสามารถคัดลอกได้

ปัญหาอยู่ใน Derived และวิธีแก้ปัญหาต้องอยู่ใน Derived: ถ้ามันต้องเป็นวัตถุแบบไดนามิกเท่านั้นที่เข้าถึงได้โดยพอยน์เตอร์หรือการอ้างอิงเท่านั้นมันคือ Derived ตัวเองที่ต้องมี

class Derived
{
    ...
    Derived(const Derived&) = delete;
    Derived& operator=(const Derived&) = delete;
};

โดยพื้นฐานแล้วมันก็ขึ้นอยู่กับผู้ออกแบบคลาส Derived (ซึ่งควรรู้ว่า Derived ทำงานอย่างไรและtheproblemมีการจัดการอย่างไร) เพื่อตัดสินใจว่าจะทำอย่างไรกับการมอบหมายและคัดลอก

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