ฉันต้องการรู้ว่า " คลาสฐานเสมือน " คืออะไรและมันหมายถึงอะไร
ให้ฉันแสดงตัวอย่าง:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
ฉันต้องการรู้ว่า " คลาสฐานเสมือน " คืออะไรและมันหมายถึงอะไร
ให้ฉันแสดงตัวอย่าง:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
คำตอบ:
คลาสพื้นฐานเสมือนที่ใช้ในการสืบทอดเสมือนเป็นวิธีการป้องกัน "อินสแตนซ์" ของคลาสที่กำหนดให้ปรากฏในลำดับชั้นการสืบทอดเมื่อใช้หลายการสืบทอด
พิจารณาสถานการณ์สมมติต่อไปนี้:
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
นี่คือสรุปย่อ สำหรับข้อมูลเพิ่มเติมได้อ่านจากนี้และนี้ เป็นตัวอย่างที่ดียังมีอยู่ที่นี่
virtual
แล้วเค้าโครงของวัตถุจะดูเหมือนเพชร และถ้าเราไม่ได้ใช้virtual
รูปแบบวัตถุจะดูเหมือนโครงสร้างต้นไม้ที่มีสองA
s
จากบันทึกด้านข้างปัญหาของ 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
และและทำให้จากC
A
ด้วยการสืบทอดปกติการแก้ไขm_iValue
จากD
จะคลุมเครือและจะต้องแก้ไข แม้ว่ามันจะมีอยู่สองm_iValues
ด้านD
ดังนั้นคุณควรจำไว้และปรับปรุงทั้งสองในเวลาเดียวกัน
ด้วยมรดกเสมือนการปรับเปลี่ยนm_iValue
จากการD
เป็น ok ... แต่ ... D
พูดเถอะว่าคุณมี C
คุณได้แนบผู้สังเกตการณ์ผ่านทางส่วนต่อประสาน และผ่านB
อินเทอร์เฟซคุณจะอัปเดตอาร์เรย์สุดเจ๋งซึ่งมีผลข้างเคียงของการเปลี่ยนแปลงโดยตรงm_iValue
...
เมื่อมีการเปลี่ยนแปลงm_iValue
โดยตรง (โดยไม่ต้องใช้วิธีการเข้าถึงเสมือน) ผู้สังเกตการณ์ "การฟัง" C
จะไม่ถูกเรียกเนื่องจากรหัสที่ใช้ในการฟังอยู่C
และB
ไม่ทราบ ...
หากคุณมีเพชรอยู่ในลำดับชั้นของคุณหมายความว่าคุณมีโอกาส 95% ที่จะทำอะไรผิดพลาดกับลำดับชั้นดังกล่าว
การอธิบายการสืบทอดหลายแบบพร้อมฐานเสมือนต้องมีความรู้เกี่ยวกับโมเดลวัตถุ C ++ และการอธิบายหัวข้ออย่างชัดเจนทำได้ดีที่สุดในบทความและไม่ได้อยู่ในช่องแสดงความคิดเห็น
คำอธิบายที่ดีที่สุดที่อ่านได้ฉันพบว่าการแก้ไขข้อสงสัยทั้งหมดของฉันในหัวข้อนี้คือบทความนี้: http://www.phpcompiler.org/articles/virtualinheritance.html
คุณไม่จำเป็นต้องอ่านอะไรอีกเลยในหัวข้อ (เว้นแต่คุณจะเป็นนักเขียนคอมไพเลอร์) หลังจากอ่านแล้ว ...
คลาสฐานเสมือนเป็นคลาสที่ไม่สามารถสร้างอินสแตนซ์ได้: คุณไม่สามารถสร้างวัตถุโดยตรงจากคลาสนั้น
ฉันคิดว่าคุณกำลังสับสนสองสิ่งที่แตกต่างกันมาก การสืบทอดเสมือนจริงไม่ใช่สิ่งเดียวกับคลาสนามธรรม การสืบทอดเสมือนจะปรับเปลี่ยนพฤติกรรมของการเรียกใช้ฟังก์ชัน บางครั้งมันแก้ไขการเรียกฟังก์ชันที่มิฉะนั้นจะคลุมเครือบางครั้งมัน defers จัดการฟังก์ชันการโทรไปยังคลาสอื่นนอกเหนือจากที่คาดว่าจะได้รับในการสืบทอดที่ไม่ใช่เสมือน
ฉันต้องการเพิ่มความกระจ่างของ 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 ได้คุณสามารถทำแบบเดียวกันภายในคลาสเหล่านี้ได้ แต่บ่อยครั้งที่มันไม่ได้เป็นเช่นนั้น
นอกจากสิ่งที่ได้กล่าวไปแล้วเกี่ยวกับการสืบทอดหลายครั้งและเสมือนจริงแล้วยังมีบทความที่น่าสนใจมากในวารสารของด๊อบบ์: การรับมรดกหลายรายการถือว่ามีประโยชน์
คุณกำลังสับสนเล็กน้อย ฉันไม่รู้ว่าคุณกำลังทำมโนทัศน์กันบ้างไหม
คุณไม่มีคลาสฐานเสมือนใน OP ของคุณ คุณมีคลาสพื้นฐาน
คุณได้รับมรดกเสมือนจริง โดยปกติจะใช้ในการสืบทอดหลายคลาสดังนั้นคลาสที่ได้รับจำนวนมากจะใช้สมาชิกของคลาสฐานโดยไม่ต้องสร้างซ้ำ
คลาสพื้นฐานที่มีฟังก์ชันเสมือนจริงไม่ได้ถูกสร้างขึ้นมา สิ่งนี้ต้องใช้ไวยากรณ์ที่พอลได้รับ โดยทั่วไปจะใช้เพื่อให้คลาสที่ได้รับต้องกำหนดฟังก์ชันเหล่านั้น
ฉันไม่ต้องการอธิบายเรื่องนี้อีกเพราะฉันไม่ได้รับสิ่งที่คุณขอทั้งหมด
มันหมายถึงการเรียกไปยังฟังก์ชั่นเสมือนจริงจะถูกส่งต่อไปยังระดับ "ขวา"
C ++ FAQ Lite FTW
กล่าวโดยย่อมักใช้ในสถานการณ์จำลองหลายมรดกที่มีลำดับชั้น "ไดมอนด์" การสืบทอดเสมือนจะทำให้เกิดความกำกวมที่สร้างในคลาสล่างเมื่อคุณเรียกใช้ฟังก์ชันในคลาสนั้นและฟังก์ชันจำเป็นต้องแก้ไขเป็นคลาส D1 หรือ D2 เหนือคลาสระดับล่างนั้น ดูรายการคำถามที่พบบ่อยสำหรับแผนภาพและรายละเอียด
มันยังใช้ในการมอบสิทธิ์น้องสาวซึ่งเป็นคุณสมบัติที่ทรงพลัง (แม้ว่าจะไม่ใช่เพราะใจอ่อน) ดูนี่สิคำถามที่พบบ่อย
ดูรายการ 40 ใน C ++ รุ่นที่ 3 ที่มีประสิทธิภาพ (43 ในรุ่นที่ 2)
ตัวอย่างการใช้งานที่สืบทอดได้ของ 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);
}
assert(A::aDefault == 0);
จากฟังก์ชั่นหลักทำให้ฉันมีข้อผิดพลาดในการรวบรวม: aDefault is not a member of A
using gcc 5.4.0 มันควรจะทำอะไร?
คลาสเสมือนไม่ได้เหมือนกับมรดกเสมือน คลาสเสมือนที่คุณไม่สามารถสร้างอินสแตนซ์ได้การสืบทอดเสมือนเป็นอย่างอื่นทั้งหมด
Wikipedia อธิบายได้ดีกว่าที่ฉันทำได้ http://en.wikipedia.org/wiki/Virtual_inheritance
ด้วยระดับทั่วไป 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 = 4
3
หลังจากนี้มันจะแทนที่ทั้ง 3 พอยน์เตอร์พอยน์เตอร์ในวัตถุที่มีพอยน์เตอร์เพื่อออฟเซ็ตในDerivedDerivedClass
vtable ของมันเพื่อเป็นตัวแทนของคลาสนั้น
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