ใน C ++ คลาสพื้นฐานเสมือนคืออะไร


403

ฉันต้องการรู้ว่า " คลาสฐานเสมือน " คืออะไรและมันหมายถึงอะไร

ให้ฉันแสดงตัวอย่าง:

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

เราควรใช้คลาสฐานเสมือนใน 'หลายมรดก' เพราะถ้าคลาส A มีสมาชิกตัวแปร int a และคลาส B ยังมีสมาชิก int a และคลาส c สืบทอดคลาส A และ B เราจะตัดสินใจว่าจะใช้ 'a' อย่างไร
Namit Sinha

2
@NamitSinha ไม่การสืบทอดเสมือนไม่สามารถแก้ปัญหานั้นได้ สมาชิก a จะไม่ชัดเจนอยู่ดี
Ichthyo

@NamitSinha การสืบทอดเสมือนไม่ใช่เครื่องมือวิเศษในการลบความคลุมเครือที่เกี่ยวข้องกับการสืบทอดหลายรายการ มัน "แก้ปัญหา" "ปัญหา" ของการมีฐานทางอ้อมมากกว่าหนึ่งครั้ง ซึ่งเป็นปัญหาถ้าตั้งใจจะแชร์เท่านั้น (บ่อยครั้ง แต่ไม่ใช่ทุกกรณี)
curiousguy

คำตอบ:


533

คลาสพื้นฐานเสมือนที่ใช้ในการสืบทอดเสมือนเป็นวิธีการป้องกัน "อินสแตนซ์" ของคลาสที่กำหนดให้ปรากฏในลำดับชั้นการสืบทอดเมื่อใช้หลายการสืบทอด

พิจารณาสถานการณ์สมมติต่อไปนี้:

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

ลำดับชั้นของคลาสด้านบนส่งผลให้ "เพชรที่หวั่น" ซึ่งมีลักษณะดังนี้:

  A
 / \
B   C
 \ /
  D

อินสแตนซ์ของ D จะถูกสร้างขึ้นจาก B ซึ่งรวมถึง A และ C ซึ่งรวมถึง A. ดังนั้นคุณจึงมี "อินสแตนซ์" สองอัน (สำหรับความต้องการในการแสดงออกที่ดีกว่า) ของ A

เมื่อคุณมีสถานการณ์นี้คุณมีความเป็นไปได้ของความคลุมเครือ จะเกิดอะไรขึ้นเมื่อคุณทำสิ่งนี้:

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

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

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

ซึ่งหมายความว่ามี "อินสแตนซ์" เดียวของ A รวมอยู่ในลำดับชั้น ด้วยเหตุนี้

D d;
d.Foo(); // no longer ambiguous

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


7
@Bohdan ไม่ไม่ :)
OJ

6
@OJ ทำไมจะไม่ล่ะ? พวกเขาเฮฮา :)
Bohdan

15
@Bohdan ใช้คำหลักเสมือนมากเท่าที่น้อยเพราะเมื่อเราใช้คำค้นหาเสมือนจะมีการใช้กลไกการรับน้ำหนักมาก ดังนั้นประสิทธิภาพของโปรแกรมของคุณจะลดลง
ซาก้า

73
ไดอะแกรม "เพชรที่น่าเกรงขาม" ของคุณเกิดความสับสน นี่คือไดอะแกรมที่แสดงความสัมพันธ์ในการสืบทอดคลาส - ไม่ใช่โครงร่างวัตถุ ส่วนที่สับสนคือถ้าเราใช้virtualแล้วเค้าโครงของวัตถุจะดูเหมือนเพชร และถ้าเราไม่ได้ใช้virtualรูปแบบวัตถุจะดูเหมือนโครงสร้างต้นไม้ที่มีสองAs
MM

5
ฉันต้องลงคะแนนคำตอบนี้ด้วยเหตุผลที่ MM อธิบายไว้ - แผนภาพแสดงสิ่งที่ตรงกันข้ามกับโพสต์
David Stone

251

เกี่ยวกับเค้าโครงหน่วยความจำ

จากบันทึกด้านข้างปัญหาของ Dreaded Diamond คือว่าคลาสพื้นฐานมีอยู่หลายครั้ง ดังนั้นด้วยมรดกอย่างสม่ำเสมอคุณเชื่อว่าคุณมี:

  A
 / \
B   C
 \ /
  D

แต่ในโครงร่างหน่วยความจำคุณมี:

A   A
|   |
B   C
 \ /
  D

สิ่งนี้อธิบายว่าทำไมเมื่อมีการโทรD::foo()คุณมีปัญหาความกำกวม แต่จริงAปัญหามาเมื่อคุณต้องการที่จะใช้ตัวแปรสมาชิกของ ตัวอย่างเช่นสมมติว่าเรามี:

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

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

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

มีอะไรผิดพลาด?

Imagine:

  • A มีคุณสมบัติพื้นฐานบางอย่าง
  • B เพิ่มไปยังข้อมูลบางประเภท (ตัวอย่าง)
  • C เพิ่มคุณสมบัติเจ๋ง ๆ บางอย่างเช่นรูปแบบผู้สังเกตการณ์ (ตัวอย่างเช่นเปิด m_iValue )
  • DสืบทอดจากBและและทำให้จากCA

ด้วยการสืบทอดปกติการแก้ไขm_iValueจากDจะคลุมเครือและจะต้องแก้ไข แม้ว่ามันจะมีอยู่สองm_iValuesด้านDดังนั้นคุณควรจำไว้และปรับปรุงทั้งสองในเวลาเดียวกัน

ด้วยมรดกเสมือนการปรับเปลี่ยนm_iValueจากการDเป็น ok ... แต่ ... Dพูดเถอะว่าคุณมี Cคุณได้แนบผู้สังเกตการณ์ผ่านทางส่วนต่อประสาน และผ่านBอินเทอร์เฟซคุณจะอัปเดตอาร์เรย์สุดเจ๋งซึ่งมีผลข้างเคียงของการเปลี่ยนแปลงโดยตรงm_iValue ...

เมื่อมีการเปลี่ยนแปลงm_iValueโดยตรง (โดยไม่ต้องใช้วิธีการเข้าถึงเสมือน) ผู้สังเกตการณ์ "การฟัง" Cจะไม่ถูกเรียกเนื่องจากรหัสที่ใช้ในการฟังอยู่CและBไม่ทราบ ...

ข้อสรุป

หากคุณมีเพชรอยู่ในลำดับชั้นของคุณหมายความว่าคุณมีโอกาส 95% ที่จะทำอะไรผิดพลาดกับลำดับชั้นดังกล่าว


'สิ่งที่อาจผิดพลาด' ของคุณเกิดจากการเข้าถึงสมาชิกฐานโดยตรงไม่ได้เกิดจากการสืบทอดหลายครั้ง กำจัด 'B "และคุณมีปัญหาเดียวกันกฎพื้นฐานของ:' ถ้าไม่ใช่แบบส่วนตัวมันควรเป็นแบบเสมือน 'เพื่อหลีกเลี่ยงปัญหา m_iValue ไม่ใช่แบบเสมือนจริงและควรเป็นแบบส่วนตัว
Chris Dodd

4
@Chris Dodd: ไม่แน่นอน สิ่งที่เกิดขึ้นกับ m_iValue จะเกิดขึ้นกับสัญลักษณ์ใด ๆ ( เช่น typedef, ตัวแปรสมาชิก, ฟังก์ชั่นสมาชิก, ส่งไปยังคลาสฐาน ฯลฯ ) นี่เป็นปัญหาการสืบทอดหลายอย่างจริง ๆ ปัญหาที่ผู้ใช้ควรระวังที่จะใช้การสืบทอดหลายอย่างถูกต้องแทนที่จะใช้วิธีการของจาวาและสรุปว่า "การสืบทอดหลาย ๆ อันนั้นเป็นสิ่งที่เลวร้าย 100%
paercebal

สวัสดีเมื่อเราใช้คำหลักเสมือนจะมีเพียงหนึ่งสำเนาของ A. คำถามของฉันคือเราจะรู้ได้อย่างไรว่ามันมาจาก B หรือ C? คำถามของฉันถูกต้องหรือไม่?
875036

@ user875036: A กำลังมาจากทั้ง B และ C แน่นอนว่า virtuality เปลี่ยนแปลงบางสิ่ง (เช่น D จะเรียกคอนสตรัคเตอร์ของ A ไม่ใช่ B หรือ C) ทั้ง B และ C (และ D) มีตัวชี้ไปที่ A.
paercebal

3
FWIW ในกรณีที่มีคนสงสัยว่าตัวแปรสมาชิกไม่สามารถเป็นเสมือน - เสมือนระบุสำหรับฟังก์ชั่น การอ้างอิง SO: stackoverflow.com/questions/3698831/…
rholmes

34

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

คำอธิบายที่ดีที่สุดที่อ่านได้ฉันพบว่าการแก้ไขข้อสงสัยทั้งหมดของฉันในหัวข้อนี้คือบทความนี้: http://www.phpcompiler.org/articles/virtualinheritance.html

คุณไม่จำเป็นต้องอ่านอะไรอีกเลยในหัวข้อ (เว้นแต่คุณจะเป็นนักเขียนคอมไพเลอร์) หลังจากอ่านแล้ว ...


10

คลาสฐานเสมือนเป็นคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้: คุณไม่สามารถสร้างวัตถุโดยตรงจากคลาสนั้น

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


7

ฉันต้องการเพิ่มความกระจ่างของ OJ

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

คุณสามารถเพิ่มอีกชั้นหนึ่งลงในเพชรเพื่อให้ได้สิ่งนี้:

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

ไม่มีคลาสใดที่สืบทอดมาอย่างแท้จริงทุกคนสืบทอดต่อสาธารณะ คลาส D21 และ D22 จะซ่อนฟังก์ชันเสมือน f () ซึ่งไม่ชัดเจนสำหรับ DD ซึ่งอาจเป็นการประกาศฟังก์ชั่นส่วนตัว พวกเขาแต่ละคนจะกำหนดฟังก์ชั่น wrapper, f1 () และ f2 () ตามลำดับแต่ละคนเรียก class-local (ส่วนตัว) f () เพื่อแก้ไขความขัดแย้ง คลาส DD เรียกใช้ f1 () ถ้าต้องการ D11 :: f () และ f2 () ถ้าต้องการ D12 :: f () หากคุณกำหนดคำว่า "อินไลน์" ไว้คุณอาจได้รับค่าโสหุ้ยเป็นศูนย์

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


2
นี่ไม่ใช่เรื่องของความสง่างามที่มากหรือน้อยหรือการแก้ไขความกำกวม (คุณสามารถใช้ข้อกำหนด xxx :: ที่ชัดเจนสำหรับสิ่งนั้นได้) ด้วยการสืบทอดที่ไม่ใช่เสมือนทุกคลาสของ DD จะมีสองอินสแตนซ์ที่เป็นอิสระของ B. ทันทีที่คลาสมีสมาชิกข้อมูลไม่คงที่เดียวการสืบทอดเสมือนและไม่ใช่เสมือนจะแตกต่างกันมากกว่าไวยากรณ์
user3489112

@ user3489112 ทันทีที่ ... ไม่มีอะไร เสมือนและไม่ใช่มรดกเสมือนความแตกต่างทางความหมายระยะเวลา
curiousguy


1

คุณกำลังสับสนเล็กน้อย ฉันไม่รู้ว่าคุณกำลังทำมโนทัศน์กันบ้างไหม

คุณไม่มีคลาสฐานเสมือนใน OP ของคุณ คุณมีคลาสพื้นฐาน

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

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

ฉันไม่ต้องการอธิบายเรื่องนี้อีกเพราะฉันไม่ได้รับสิ่งที่คุณขอทั้งหมด


1
"คลาสพื้นฐาน" ที่ใช้ในการสืบทอดเสมือนจะกลายเป็น "คลาสพื้นฐานเสมือน" (ในบริบทของการสืบทอดที่แม่นยำนั้น)
Luc Hermitte

1

มันหมายถึงการเรียกไปยังฟังก์ชั่นเสมือนจริงจะถูกส่งต่อไปยังระดับ "ขวา"

C ++ FAQ Lite FTW

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

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

ดูรายการ 40 ใน C ++ รุ่นที่ 3 ที่มีประสิทธิภาพ (43 ในรุ่นที่ 2)


1

ตัวอย่างการใช้งานที่สืบทอดได้ของ Diamond

ตัวอย่างนี้แสดงวิธีใช้คลาสฐานเสมือนในสถานการณ์ทั่วไป: เพื่อแก้ปัญหาการสืบทอดเพชร

#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}

2
assert(A::aDefault == 0);จากฟังก์ชั่นหลักทำให้ฉันมีข้อผิดพลาดในการรวบรวม: aDefault is not a member of Ausing gcc 5.4.0 มันควรจะทำอะไร?
SebNag

@SebTu อ่าขอบคุณสิ่งที่ฉันลืมที่จะลบออกจากการคัดลอกวางออกตอนนี้ ตัวอย่างควรยังคงมีความหมายหากไม่มีมัน
Ciro Santilli 法轮功冠状病六四事件法轮功

0

คลาสเสมือนไม่ได้เหมือนกับมรดกเสมือน คลาสเสมือนที่คุณไม่สามารถสร้างอินสแตนซ์ได้การสืบทอดเสมือนเป็นอย่างอื่นทั้งหมด

Wikipedia อธิบายได้ดีกว่าที่ฉันทำได้ http://en.wikipedia.org/wiki/Virtual_inheritance


6
ไม่มีสิ่งเช่น "คลาสเสมือน" ใน C ++ อย่างไรก็ตามมี "คลาสฐานเสมือน" ซึ่งเป็น "เสมือน" เกี่ยวกับการสืบทอดที่กำหนด สิ่งที่คุณอ้างถึงคือสิ่งที่เรียกว่า "คลาสนามธรรม" อย่างเป็นทางการ
Luc Hermitte

@LucHermitte มีคลาสเสมือนจริงใน C ++ ตรวจสอบเรื่องนี้: en.wikipedia.org/wiki/Virtual_class
Rafid

"ข้อผิดพลาด: สามารถระบุ 'เสมือน' สำหรับฟังก์ชันได้เท่านั้น" ฉันไม่รู้ว่านี่คือภาษาอะไร แต่ไม่มีสิ่งแน่นอนเช่นคลาสเสมือนใน C ++
Luc Hermitte

0

มรดกปกติ

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

ใหม่มีลายเซ็น:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)

และทำการโทรออก mallocกลับเป็นตัวชี้โมฆะ

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

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

ที่จริงเมื่อฉันพูดตัวชี้ไปที่ตารางเสมือนจริงมันมักจะชดเชย 16 ลงในตารางเสมือน

vtable for Base:
        .quad   0
        .quad   typeinfo for Base
        .quad   Base::CommonFunction()
        .quad   Base::VirtualFunction()

pointer is typically to the first function i.e. 

        mov     edx, OFFSET FLAT:vtable for Base+16

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

override เป็นคอมไพเลอร์ตัวป้องกันอีกตัวที่บอกว่าฟังก์ชั่นนี้มีความสำคัญมากกว่าบางอย่างและถ้ามันไม่ได้เกิดข้อผิดพลาดของคอมไพเลอร์

= 0 หมายความว่านี่เป็นฟังก์ชันนามธรรม

final ป้องกันไม่ให้มีการใช้งานฟังก์ชันเสมือนอีกครั้งในคลาสที่ได้รับมากขึ้นและจะทำให้แน่ใจว่าตารางเสมือนของคลาสที่ได้รับมาส่วนใหญ่จะมีฟังก์ชันสุดท้ายของคลาสนั้น

= default ทำให้ชัดเจนในเอกสารประกอบว่าคอมไพเลอร์จะใช้การใช้งานเริ่มต้น

= delete ให้ข้อผิดพลาดคอมไพเลอร์หากพยายามโทรนี้

มรดกเสมือน

พิจารณา

class Base
  {
      int a = 1;
      int b = 2;
  public:
      void virtual CommonFunction(){} ;
      void virtual VirtualFunction(){} ;
  };


class DerivedClass1: virtual public Base
  {
      int c = 3;
  public:
    void virtual DerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
  };

  class DerivedClass2 : virtual public Base
 {
     int d = 4;
 public:
     //void virtual DerivedCommonFunction(){} ;    
     void virtual VirtualFunction(){} ;
     void virtual DerivedCommonFunction2(){} ;
 };

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
 {
   int e = 5;
 public:
     void virtual DerivedDerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
 };

 int main () {
   DerivedDerivedClass* d = new DerivedDerivedClass;
   d->VirtualFunction();
   d->DerivedCommonFunction();
   d->DerivedCommonFunction2();
   d->DerivedDerivedCommonFunction();
   ((DerivedClass2*)d)->DerivedCommonFunction2();
   ((Base*)d)->VirtualFunction();
 }

คุณจะได้รับวัตถุที่มีลักษณะดังนี้:

แทนสิ่งนี้:

นั่นคือจะมีวัตถุฐาน 2

ในสถานการณ์การสืบทอดเพชรเสมือนด้านบนหลังจากใหม่ถูกเรียกมันเรียกนวกรรมิกที่ได้รับมามากที่สุดและในนวกรรมิกนั้นมันเรียกสิ่งก่อสร้างที่ได้รับมาทั้ง 3 ตัวที่ผ่านการหักล้างเข้าไปในตารางตารางเสมือนแทนที่จะเรียกเพียงDerivedClass1::DerivedClass1()และDerivedClass2::DerivedClass2()แล้วที่ทั้งสองได้โทรBase::Base()

ต่อไปนี้คือทั้งหมดที่รวบรวมในโหมดการแก้ปัญหา -O0 ดังนั้นจะมีการชุมนุมซ้ำซ้อน

main:
.LFB8:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 48 //pass size to new
        call    operator new(unsigned long) //call new
        mov     rbx, rax  //move the address of the allocation to rbx
        mov     rdi, rbx  //move it to rdi i.e. pass to the call
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
        mov     QWORD PTR [rbp-24], rbx  //store the address of the object on the stack as d
DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB5:
        mov     rax, QWORD PTR [rbp-8] // object address now in rax 
        add     rax, 32 //increment address by 32
        mov     rdi, rax // move object address+32 to rdi i.e. pass to call
        call    Base::Base() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
        mov     rsi, rdx //pass VTT+8 address as 2nd parameter 
        mov     rdi, rax //object address as first
        call    DerivedClass1::DerivedClass1() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        add     rax, 16  //increment object address by 16
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+24  //store address of VTT+24 in edx
        mov     rsi, rdx //pass address of VTT+24 as second parameter
        mov     rdi, rax //address of object as first
        call    DerivedClass2::DerivedClass2() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        mov     QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        add     rax, 32  // increment object address by 32
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
        mov     QWORD PTR [rax], rdx  //store vtable for DerivedDerivedClass+120 at object+32 (Base) 
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax+28], 5
.LBE5:
        nop
        leave
        ret

มันเรียกBase::Base()ด้วยตัวชี้ไปยังวัตถุชดเชย 32 ฐานเก็บตัวชี้ไปยังตารางเสมือนที่อยู่ที่ได้รับและสมาชิกหลังจากนั้น

Base::Base() [base object constructor]:
.LFB11:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
        mov     edx, OFFSET FLAT:vtable for Base+16  //puts vtable for Base+16 in edx
        mov     rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
        mov     QWORD PTR [rax], rdx  //stores it address of object
        mov     rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
        mov     DWORD PTR [rax+8], 1 //stores a = 1 in the object
        mov     rax, QWORD PTR [rbp-8] //junk from -O0
        mov     DWORD PTR [rax+12], 2  //stores b = 2 in the object
.LBE2:
        nop
        pop     rbp
        ret

DerivedDerivedClass::DerivedDerivedClass()จากนั้นเรียกใช้DerivedClass1::DerivedClass1()พร้อมกับตัวชี้ไปยังอ็อฟเซ็ต 0 และส่งผ่านที่อยู่ของVTT for DerivedDerivedClass+8

DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //address of object
        mov     QWORD PTR [rbp-16], rsi  //address of VTT+8
.LBB3:
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rdx, QWORD PTR [rax]     //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
        mov     rax, QWORD PTR [rbp-8]   //address of object now in rax
        mov     QWORD PTR [rax], rdx     //store address of DerivedClass1-in-.. in the object
        mov     rax, QWORD PTR [rbp-8]  // address of object now in rax
        mov     rax, QWORD PTR [rax]    //address of DerivedClass1-in.. now implicitly in rax
        sub     rax, 24                 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
        mov     rax, QWORD PTR [rax]    //value of 32 now in rax
        mov     rdx, rax                // now in rdx
        mov     rax, QWORD PTR [rbp-8]  //address of object now in rax
        add     rdx, rax                //address of object+32 now in rdx
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rax, QWORD PTR [rax+8]   //address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
        mov     QWORD PTR [rdx], rax     //store at address object+32 (offset to Base)
        mov     rax, QWORD PTR [rbp-8]  //store address of object in rax, return
        mov     DWORD PTR [rax+8], 3    //store its attribute c = 3 in the object
.LBE3:
        nop
        pop     rbp
        ret
VTT for DerivedDerivedClass:
        .quad   vtable for DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
        .quad   vtable for DerivedDerivedClass+120
        .quad   vtable for DerivedDerivedClass+72

construction vtable for DerivedClass1-in-DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedClass1
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedClass1::VirtualFunction()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedClass1
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
        .quad   16
        .quad   0
        .quad   typeinfo for DerivedClass2
        .quad   DerivedClass2::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -16
        .quad   0
        .quad   -16
        .quad   typeinfo for DerivedClass2
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedDerivedClass
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedDerivedClass::VirtualFunction()
        .quad   DerivedDerivedClass::DerivedDerivedCommonFunction()
        .quad   16
        .quad   -16
        .quad   typeinfo for DerivedDerivedClass
        .quad   non-virtual thunk to DerivedDerivedClass::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedDerivedClass
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedDerivedClass::VirtualFunction()

virtual thunk to DerivedClass1::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
        sub     rdi, 16
        jmp     .LTHUNK3

        .set    .LTHUNK0,DerivedClass1::VirtualFunction()
        .set    .LTHUNK1,DerivedClass2::VirtualFunction()
        .set    .LTHUNK2,DerivedDerivedClass::VirtualFunction()
        .set    .LTHUNK3,DerivedDerivedClass::VirtualFunction()

DerivedDerivedClass::DerivedDerivedClass()แล้วผ่านที่อยู่ของวัตถุ + 16 และที่อยู่ของ VTT สำหรับที่DerivedDerivedClass+24จะDerivedClass2::DerivedClass2()มีการชุมนุมเป็นเหมือนDerivedClass1::DerivedClass1()ยกเว้นบรรทัดmov DWORD PTR [rax+8], 3ซึ่งเห็นได้ชัดว่ามี 4 แทน d = 43

หลังจากนี้มันจะแทนที่ทั้ง 3 พอยน์เตอร์พอยน์เตอร์ในวัตถุที่มีพอยน์เตอร์เพื่อออฟเซ็ตในDerivedDerivedClassvtable ของมันเพื่อเป็นตัวแทนของคลาสนั้น

d->VirtualFunction();:

        mov     rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax 
        mov     rax, QWORD PTR [rax] //dereference and store in rax
        add     rax, 8 // call the 2nd function in the table
        mov     rdx, QWORD PTR [rax] //dereference 
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction2();:

        mov     rax, QWORD PTR [rbp-24]
        lea     rdx, [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+16]
        add     rax, 8
        mov     rax, QWORD PTR [rax]
        mov     rdi, rdx
        call    rax

d->DerivedDerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        add     rax, 16
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

((DerivedClass2*)d)->DerivedCommonFunction2();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L14
        mov     rax, QWORD PTR [rbp-24]
        add     rax, 16
        jmp     .L15
.L14:
        mov     eax, 0
.L15:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L18
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, 16
        jmp     .L19
.L18:
        mov     edx, 0
.L19:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

((Base*)d)->VirtualFunction();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L20
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        sub     rax, 24
        mov     rax, QWORD PTR [rax]
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        jmp     .L21
.L20:
        mov     eax, 0
.L21:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L24
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        sub     rdx, 24
        mov     rdx, QWORD PTR [rdx]
        mov     rcx, rdx
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, rcx
        jmp     .L25
.L24:
        mov     edx, 0
.L25:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.