ทำไมคอนสตรัคเตอร์ถึงไม่ได้รับมรดก


33

ฉันสับสนว่าปัญหาจะเกิดขึ้นได้อย่างไรหากคอนสตรัคเตอร์ได้รับมรดกจากคลาสฐาน Cpp Primer Plus กล่าวว่า

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

ฉันเข้าใจว่าตัวสร้างถูกเรียกก่อนการสร้างวัตถุจะเสร็จสมบูรณ์

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

ฉันเข้าใจว่าไม่มีความจำเป็นที่จะต้องเรียกคอนสตรัคเตอร์อย่างชัดเจนจากภายในโค้ด [ไม่ใช่สิ่งที่ฉันรู้ขณะนี้] ยกเว้นขณะสร้างวัตถุ แม้ว่าคุณจะสามารถใช้กลไกบางอย่างในการเรียก const constor ของ parent [ใน cpp, การใช้::หรือการใช้member initialiser list, In java using super] ใน Java มีการบังคับให้เรียกมันในบรรทัดที่ 1 ฉันเข้าใจว่าเพื่อให้แน่ใจว่าวัตถุแม่ถูกสร้างขึ้นก่อนแล้วจึงสร้างการสร้างวัตถุลูก

มันสามารถแทนที่มัน แต่ฉันไม่สามารถคิดสถานการณ์ที่ทำให้เกิดปัญหาได้ หากเด็กได้สืบทอดคอนสตรัคเตอร์หลักสิ่งที่สามารถผิดพลาดได้?

ดังนั้นนี่คือเพื่อหลีกเลี่ยงการสืบทอดฟังก์ชันที่ไม่จำเป็น หรือมีมากไปไหม


ไม่ถึงความเร็วด้วย C ++ 11 แต่ฉันคิดว่ามันมีรูปแบบของตัวสร้างอยู่ในนั้น
yannis

3
โปรดจำไว้ว่าสิ่งที่คุณทำในการก่อสร้างคุณจะต้องดูแลใน destructors
Manoj R

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

1
ขอบคุณสำหรับความกระจ่างในการสืบทอดคอนสตรัคเตอร์ตอนนี้คุณสามารถรับคำตอบได้
Dunk

คำตอบ:


41

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

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

ใน C ++ 11 มีการแนะนำรูปแบบของ 'constructor inheritance' ซึ่งคุณสามารถสั่งให้คอมไพเลอร์สร้างชุดของ constructors สำหรับคุณที่รับอาร์กิวเมนต์เดียวกับ Constructor จากคลาสฐานและส่งต่ออาร์กิวเมนต์เหล่านั้นไปยัง ชั้นฐาน
แม้ว่าจะเรียกว่าการสืบทอดอย่างเป็นทางการ แต่ก็ไม่ได้เป็นเช่นนั้นเพราะยังมีฟังก์ชั่นเฉพาะที่ได้รับมา ตอนนี้มันเพิ่งถูกสร้างโดยคอมไพเลอร์แทนที่จะเขียนโดยคุณอย่างชัดเจน

คุณลักษณะนี้ทำงานเช่นนี้:

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derivedตอนนี้มีสอง constructors (ไม่นับตัวสร้าง copy / move) สิ่งหนึ่งที่รับค่า int และ string และอีกอันหนึ่งที่รับค่า int


ดังนั้นนี่คือสมมุติว่าคลาสเด็กไม่มีคอนสตรัคเตอร์ของมันเอง? และ
ผู้รับเหมาก่อสร้าง

C ++ ถูกกำหนดในลักษณะที่เป็นไปไม่ได้สำหรับคลาสที่ไม่มีคอนสตรัคเตอร์ของตัวเอง นั่นคือเหตุผลที่ฉันไม่คิดว่า 'ผู้รับมรดกของคอนสตรัคเตอร์' เป็นมรดกจริงๆ มันเป็นวิธีการหลีกเลี่ยงการเขียนสำเร็จรูปน่าเบื่อ
Bart van Ingen Schenau

2
ฉันคิดว่าความสับสนเกิดขึ้นจากความจริงที่ว่าแม้แต่ผู้สร้างที่ว่างเปล่าก็ทำงานเบื้องหลังได้ คอนสตรัคเตอร์มีสองส่วนจริงๆ: ผลงานภายในและส่วนที่คุณเขียน เนื่องจากพวกเขาไม่ได้แยกกันก่อสร้างไม่ได้รับมรดก
ซารีน

1
โปรดลองพิจารณาการระบุว่าm()มาจากไหนและจะเปลี่ยนไปอย่างไรถ้าเป็นตัวอย่างintประเภท
Deduplicator

เป็นไปได้ไหมที่จะทำโดยusing Base::Baseนัย? มันจะมีผลขนาดใหญ่ถ้าฉันลืมบรรทัดนั้นในคลาสที่ได้รับและฉันต้องสืบทอดตัวสร้างในคลาสที่ได้รับทั้งหมด
โพสต์ด้วยตัวเอง

7

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

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

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

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

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


การตีความครั้งที่สองหรือคำถามเพิ่มเติมคือ - จะเกิดอะไรขึ้นถ้าตัวสร้างคลาสฐานปรากฏขึ้นโดยอัตโนมัติในคลาสที่ได้รับทั้งหมดเว้นแต่จะซ่อนไว้อย่างชัดเจน

หากเด็กได้สืบทอดคอนสตรัคเตอร์หลักสิ่งที่สามารถผิดพลาดได้?

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

ตัวอย่างทั่วไปคือสี่เหลี่ยมและสี่เหลี่ยม (โปรดสังเกตว่าสี่เหลี่ยมและสี่เหลี่ยมโดยทั่วไปไม่ใช่ Liskov-substituted ดังนั้นจึงไม่ใช่การออกแบบที่ดีมาก แต่เน้นให้เห็นถึงปัญหา)

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

หาก Square ได้รับค่าคอนสตรัคเตอร์สองค่าของ Rectangle คุณสามารถสร้างสี่เหลี่ยมที่มีความสูงและความกว้างต่างกัน ... นั่นเป็นเหตุผลทางตรรกะดังนั้นคุณจึงต้องการซ่อนตัวสร้างนั้น


3

ทำไมคอนสตรัคเตอร์ไม่สืบทอด: คำตอบง่าย ๆ อย่างน่าประหลาดใจ: คอนสตรัคเตอร์ของคลาสฐาน "สร้าง" คลาสพื้นฐานและตัวสร้างของคลาสที่สืบทอด "สร้าง" คลาสที่สืบทอด ถ้าคลาสที่สืบทอดมาจะสืบทอดตัวสร้างตัวสร้างจะพยายามสร้างวัตถุประเภทคลาสพื้นฐานและคุณจะไม่สามารถ "สร้าง" วัตถุประเภทคลาสที่สืบทอดมา

ชนิดของการเอาชนะจุดประสงค์ของการสืบทอดชั้นเรียน


3

ปัญหาที่ชัดเจนที่สุดในการอนุญาตให้คลาสที่ได้รับมาแทนที่ตัวสร้างคลาสพื้นฐานคือผู้พัฒนาคลาสที่ได้รับนั้นมีหน้าที่รับผิดชอบในการทราบวิธีการสร้างคลาสพื้นฐาน (es) จะเกิดอะไรขึ้นเมื่อคลาสที่ได้รับไม่สร้างคลาสพื้นฐานอย่างเหมาะสม?

นอกจากนี้หลักการการทดแทน Liskov จะไม่นำมาใช้อีกต่อไปเนื่องจากคุณไม่สามารถพึ่งพาคอลเลกชันของวัตถุคลาสพื้นฐานเพื่อให้เข้ากันได้อีกต่อไปเนื่องจากไม่มีการรับประกันว่าคลาสพื้นฐานถูกสร้างอย่างถูกต้องหรือเข้ากันได้กับประเภทอื่น ๆ

มันมีความซับซ้อนมากขึ้นเมื่อมีการเพิ่มการสืบทอดมากกว่า 1 ระดับ ตอนนี้คลาสที่ได้รับของคุณจำเป็นต้องรู้วิธีการสร้างคลาสพื้นฐานทั้งหมดในห่วงโซ่

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


2

คอนสตรัคเตอร์นั้นแตกต่างจากวิธีอื่นอย่างสิ้นเชิง:

  1. พวกเขาจะถูกสร้างขึ้นหากคุณไม่ได้เขียนพวกเขา
  2. ตัวสร้างคลาสพื้นฐานทั้งหมดถูกเรียกโดยปริยายแม้ว่าคุณจะไม่ทำด้วยตนเอง
  3. คุณไม่ได้เรียกพวกเขาอย่างชัดเจน แต่โดยการสร้างวัตถุ

เหตุใดพวกเขาจึงไม่สืบทอด คำตอบง่ายๆ: เนื่องจากมีการแทนที่อยู่เสมอสร้างหรือเขียนด้วยตนเอง

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


1

ทุกชั้นเรียนต้องการคอนสตรัคเตอร์
C ++ จะสร้างตัวสร้างเริ่มต้นสำหรับคุณยกเว้นถ้าคุณสร้างนวกรรมิกพิเศษ
ในกรณีที่คลาสพื้นฐานของคุณใช้ตัวสร้างแบบพิเศษคุณจะต้องเขียนนวกรรมิกพิเศษบนคลาสที่ได้รับแม้ว่าทั้งคู่จะเหมือนกันและโยงกลับมา
C ++ 11 ช่วยให้คุณสามารถหลีกเลี่ยงการทำสำเนารหัสในตัวสร้างโดยใช้ :

Class A : public B {
using B:B;
....
  1. ชุดของตัวสร้างการสืบทอดประกอบด้วย

    • คอนสตรัคเตอร์ที่ไม่ใช่เท็มเพลตทั้งหมดของคลาสฐาน (หลังจากละเว้นพารามิเตอร์จุดไข่ปลาถ้ามี) (ตั้งแต่ C ++ 14)
    • สำหรับคอนสตรัคเตอร์แต่ละตัวที่มีอาร์กิวเมนต์เริ่มต้นหรือพารามิเตอร์จุดไข่ปลาตัวสร้างคอนสตรัคทั้งหมดที่เกิดขึ้นจากการปล่อยจุดไข่ปลาและละเว้นการขัดแย้งเริ่มต้นจากจุดสิ้นสุดของอาร์กิวเมนต์แสดงทีละหนึ่ง
    • เทมเพลตตัวสร้างทั้งหมดของคลาสฐาน (หลังจากละเว้นพารามิเตอร์จุดไข่ปลาถ้ามี) (ตั้งแต่ C ++ 14)
    • สำหรับเทมเพลตคอนสตรัคเตอร์แต่ละตัวที่มีอาร์กิวเมนต์เริ่มต้นหรือจุดไข่ปลาตัวสร้างคอนสตรัคเตอร์ทั้งหมดที่เกิดขึ้นจากการปล่อยจุดไข่ปลาและละเว้นอาร์กิวเมนต์เริ่มต้นจากส่วนท้ายของอาร์กิวเมนต์จะแสดงทีละรายการ
  2. ตัวสร้างที่สืบทอดทั้งหมดที่ไม่ใช่ตัวสร้างเริ่มต้นหรือตัวสร้างสำเนา / ย้ายและมีลายเซ็นไม่ตรงกับตัวสร้างที่ผู้ใช้กำหนดในคลาสที่ได้รับจะถูกประกาศโดยนัยในคลาสที่ได้รับ พารามิเตอร์เริ่มต้นจะไม่สืบทอด


0

คุณสามารถใช้ได้:

MyClass() : Base()

คุณถามว่าทำไมคุณต้องทำเช่นนี้?

คลาสย่อยอาจมีคุณสมบัติเพิ่มเติมซึ่งอาจต้องมีการกำหนดค่าเริ่มต้นในตัวสร้างหรืออาจกำหนดค่าเริ่มต้นตัวแปรคลาสพื้นฐานในวิธีที่แตกต่างกัน

คุณจะสร้างวัตถุประเภทย่อยได้อย่างไร


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