C ++ มีวิธีจัดการหลายมรดกด้วยบรรพบุรุษร่วมกันอย่างไร


13

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


3
เพื่อความสมบูรณ์ "ปัญหาเพชร" คืออะไรโปรด
jcolebrand

1
@jcolebrand en.wikipedia.org/wiki/Multiple_inheritanceดูปัญหาเพชร
l46kok

1
แน่นอนฉัน googled แต่ฉันจะรู้ได้อย่างไรว่านั่นคือสิ่งที่ Sandeep หมายถึง? หากคุณกำลังจะไปยังสิ่งที่อ้างอิงจากชื่อปิดบังเมื่อ "มรดกหลายที่มีบรรพบุรุษร่วมกัน" เป็นโดยตรงมากขึ้น ...
jcolebrand

1
@ jcolebrand ฉันแก้ไขมันเพื่อสะท้อนสิ่งที่ฉันได้จากคำถาม ฉันคิดว่าเขาหมายถึงปัญหาเพชรที่อ้างอิงกับวิกิพีเดียจากบริบท ครั้งต่อไปลดลงแสดงความคิดเห็นที่เป็นมิตรและใช้มันใหม่เครื่องมือแก้ไขปัญหา :)
Earlz

1
@Elz ใช้งานได้เมื่อฉันเข้าใจการอ้างอิงเท่านั้น มิฉะนั้นฉันจะแก้ไขไม่ดีและนั่นก็เป็นเพียง juju ไม่ดี
jcolebrand

คำตอบ:


24

เหตุใดจึงมีการสืบทอดหลายรายการใน C ++ แต่ไม่ใช่ใน C #

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

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

C ++ แก้ไขความคลุมเครือของลายเซ็นวิธีที่เหมือนกันซึ่งสืบทอดมาจากคลาสพื้นฐานหลายคลาสได้อย่างไร

มันไม่ได้ มีไวยากรณ์ในการเรียกใช้เมธอดคลาสพื้นฐานจากฐานเฉพาะอย่างชัดเจน แต่ไม่มีวิธีการแทนที่หนึ่งในเมธอดเสมือนเท่านั้นและถ้าคุณไม่แทนที่เมธอดในคลาสย่อยคุณจะไม่สามารถเรียกมันได้โดยไม่ต้องระบุฐาน ชั้น

และทำไมการออกแบบเดียวกันไม่รวมอยู่ใน C #

ไม่มีอะไรจะรวมเป็น


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

ส่วนต่อประสานใน Java และ C # ถูก จำกัด ให้ประกาศวิธีการเท่านั้น แต่วิธีการจะต้องมีการดำเนินการในแต่ละชั้นเรียนที่ได้รับส่วนต่อประสาน อย่างไรก็ตามมีอินเทอร์เฟซขนาดใหญ่ซึ่งมันจะมีประโยชน์ในการเตรียมการใช้งานเริ่มต้นของวิธีการบางอย่างในแง่ของผู้อื่น ตัวอย่างทั่วไปเปรียบได้ (ในภาษาเทียม):

mixin IComparable {
    public bool operator<(IComparable r) = 0;
    public bool operator>(IComparable r) { return r < this; }
    public bool operator<=(IComparable r) { return !(r < this); }
    public bool operator>=(IComparable r) { return !(r > this); }
    public bool operator==(IComparable r) { return !(r < this) && !(r > this); }
    public bool operator!=(IComparable r) { return r < this || r > this; }
};

ความแตกต่างจากคลาสเต็มคือสิ่งนี้ไม่สามารถมีข้อมูลสมาชิกได้ มีหลายตัวเลือกสำหรับการใช้งานนี้ เห็นได้ชัดว่าการสืบทอดหลายรายการเป็นหนึ่ง แต่การรับมรดกหลายรายการค่อนข้างซับซ้อนกว่าที่จะใช้ แต่มันไม่จำเป็นจริงๆที่นี่ แต่หลายภาษาใช้สิ่งนี้โดยแยกมิกซ์อินในอินเทอร์เฟซซึ่งถูกนำไปใช้โดยคลาสและที่เก็บของการใช้งานเมธอดซึ่งถูกแทรกเข้าไปในคลาสนั้นเองหรือสร้างคลาสพื้นฐานระดับกลางและวางไว้ที่นั่น สิ่งนี้ถูกนำไปใช้ใน Ruby และD , จะถูกนำมาใช้ใน Java 8 และสามารถนำไปใช้งานด้วยตนเองใน C ++ โดยใช้รูปแบบเทมเพลตที่เกิดขึ้นซ้ำๆ ข้างต้นในรูปแบบ CRTP ดูเหมือนว่า:

template <typename Derived>
class IComparable {
    const Derived &_d() const { return static_cast<const Derived &>(*this); }
public:
    bool operator>(const IComparable &r) const { r._d() < _d(); }
    bool operator<=(const IComparable &r) const { !(r._d() < _d(); }
    ...
};

และใช้เหมือน:

class Concrete : public IComparable<Concrete> { ... };

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

template <typename Derived, typename Base>
class IComparable : public Base { ... };
class Concrete : public IComparable<Concrete, Base> { ... };

การใช้งานคอมไพเลอร์อาจหรือไม่อาจหลีกเลี่ยงการจัดส่งเสมือน

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


หนึ่งอาจต้องการพูดถึงว่าหลายมรดกจะได้รับการแนะนำใน Java 8 ด้วยวิธีการขยายส่วนต่อประสาน
Giorgio

@Giorgio: ไม่มรดกหลายอย่างจะไม่ถูกนำมาใช้ใน Java Mixins จะเป็นสิ่งที่แตกต่างกันมากแม้ว่ามันจะครอบคลุมเหตุผลที่เหลืออยู่มากมายที่จะใช้การสืบทอดหลายแบบและเหตุผลส่วนใหญ่ที่จะใช้รูปแบบเทมเพลต (CRTP) ที่เกิดขึ้นอย่างแปลกประหลาดและทำงานส่วนใหญ่เช่น CRTP ไม่ใช่การสืบทอดหลาย ๆ
Jan Hudec

ฉันคิดว่าการสืบทอดหลายรายการไม่ต้องการพอยน์เตอร์เข้ากลางวัตถุ ถ้าเป็นเช่นนั้นการสืบทอดหลายอินเตอร์เฟสจะต้องใช้เช่นกัน
svick

@svick: ไม่มันไม่ได้ แต่ทางเลือกนั้นมีประสิทธิภาพน้อยกว่ามากเนื่องจากต้องการการส่งแบบเสมือนสำหรับการเข้าถึงสมาชิก
Jan Hudec

+1 สำหรับคำตอบที่สมบูรณ์มากกว่าคำตอบ
Nathan C. Tresch

2

คำตอบคือมันทำงานไม่ถูกต้องใน C ++ ในกรณีที่ namespace clashes ดูนี่สิ เพื่อหลีกเลี่ยงการกระทบ namespace คุณต้องทำ gyrations ทุกชนิดด้วยพอยน์เตอร์ ฉันทำงานที่ MS ในทีม Visual Studio และอย่างน้อยก็ในส่วนที่พวกเขาพัฒนาคณะผู้แทนก็เพื่อหลีกเลี่ยงการชนกันของชื่อ Preiouvsly ฉันได้กล่าวว่าพวกเขายังถือว่าการเชื่อมต่อเป็นส่วนหนึ่งของการแก้ปัญหามรดกหลาย แต่ฉันเข้าใจผิด อินเทอร์เฟซน่าทึ่งจริง ๆ และสามารถทำงานใน C ++, FWIW

การมอบหมายงานเน้นที่การชนกันของเนมสเปซ: คุณสามารถมอบสิทธิ์ให้กับ 5 คลาสและทั้ง 5 คนจะส่งออกเมธอดไปยังขอบเขตของคุณในฐานะสมาชิกชั้นหนึ่ง ด้านนอกมองในนี้เป็นมรดกที่หลากหลาย


1
ฉันพบว่าไม่น่าเป็นไปได้มากว่านี่คือเหตุผลหลักที่ไม่รวม MI ใน C # นอกจากนี้โพสต์นี้ไม่ตอบคำถามว่าทำไม MI ทำงานใน C ++ เมื่อคุณไม่มี "namespace clashes"
Doc Brown

@DocBrown ฉันทำงานที่ MS ในทีม Visual Studio และฉันรับรองว่าอย่างน้อยก็ในส่วนของเหตุผลที่พวกเขาพัฒนาคณะผู้แทนและอินเทอร์เฟซเพื่อหลีกเลี่ยงการชนกันของ namespace ทั้งหมด สำหรับคุณภาพของคำถาม meh ใช้ downvote ของคุณคนอื่น ๆ คิดว่ามันมีประโยชน์
Nathan C. Tresch

1
ฉันไม่ได้ตั้งใจจะลงคะแนนคำตอบของคุณเพราะฉันคิดว่ามันถูกต้อง แต่คำตอบของคุณอ้างว่า MI ไม่สามารถใช้ได้อย่างสมบูรณ์ใน C ++ และนั่นคือเหตุผลที่พวกเขาไม่ได้แนะนำใน C # แต่ผมไม่ชอบ MI มากใน C ++ ผมคิดว่ามันเป็นไปไม่ได้อย่างสมบูรณ์ unuasable
Doc Brown

1
มันเป็นหนึ่งในเหตุผลและฉันก็ไม่ได้หมายความว่ามันไม่ได้ผล เมื่อมีบางอย่างที่ไม่สามารถกำหนดได้ในการคำนวณฉันมักจะบอกว่ามัน "แตก" หรือว่า "ไม่ทำงาน" เมื่อฉันตั้งใจจะหมายถึง "มันเหมือนของขึ้นวูดูมันอาจจะใช่หรือไม่ทำงาน : D
นาธานซี Tresch

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