Tôi muốn biết " lớp cơ sở ảo " là gì và ý nghĩa của nó.
Hãy để tôi chỉ ra một ví dụ:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Tôi muốn biết " lớp cơ sở ảo " là gì và ý nghĩa của nó.
Hãy để tôi chỉ ra một ví dụ:
class Foo
{
public:
void DoSomething() { /* ... */ }
};
class Bar : public virtual Foo
{
public:
void DoSpecific() { /* ... */ }
};
Câu trả lời:
Các lớp cơ sở ảo, được sử dụng trong kế thừa ảo, là một cách để ngăn chặn nhiều "trường hợp" của một lớp nhất định xuất hiện trong hệ thống phân cấp thừa kế khi sử dụng nhiều kế thừa.
Hãy xem xét kịch bản sau đây:
class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};
Hệ thống phân cấp lớp ở trên dẫn đến "viên kim cương đáng sợ" trông như thế này:
A
/ \
B C
\ /
D
Một thể hiện của D sẽ được tạo thành từ B, bao gồm A và C cũng bao gồm A. Vì vậy, bạn có hai "trường hợp" (vì muốn có biểu thức tốt hơn) của A.
Khi bạn có kịch bản này, bạn có khả năng mơ hồ. Điều gì xảy ra khi bạn làm điều này:
D d;
d.Foo(); // is this B's Foo() or C's Foo() ??
Kế thừa ảo là có để giải quyết vấn đề này. Khi bạn chỉ định ảo khi kế thừa các lớp của mình, bạn đang nói với trình biên dịch rằng bạn chỉ muốn một cá thể.
class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
Điều này có nghĩa là chỉ có một "thể hiện" của A được bao gồm trong cấu trúc phân cấp. Vì thế
D d;
d.Foo(); // no longer ambiguous
Đây là một bản tóm tắt nhỏ. Để biết thêm thông tin, hãy đọc cái này và cái này . Một ví dụ tốt cũng có sẵn ở đây .
virtual
, thì bố cục đối tượng trông giống như kim cương; và nếu chúng ta không sử dụng virtual
thì bố cục đối tượng trông giống như một cấu trúc cây chứa hai A
s
Một lưu ý phụ, vấn đề với Kim cương đáng sợ là lớp cơ sở có mặt nhiều lần. Vì vậy, với thừa kế thường xuyên, bạn tin rằng bạn có:
A
/ \
B C
\ /
D
Nhưng trong cách bố trí bộ nhớ, bạn có:
A A
| |
B C
\ /
D
Điều này giải thích tại sao khi gọi D::foo()
, bạn có một vấn đề mơ hồ. Nhưng vấn đề thực sự xảy ra khi bạn muốn sử dụng biến thành viên A
. Ví dụ: giả sử chúng ta có:
class A
{
public :
foo() ;
int m_iValue ;
} ;
Khi bạn sẽ cố gắng truy cập m_iValue
từ D
, trình biên dịch sẽ phản đối, bởi vì trong hệ thống phân cấp, nó sẽ thấy hai m_iValue
chứ không phải một. Và nếu bạn sửa đổi một, giả sử, B::m_iValue
(đó là A::m_iValue
cha mẹ của B
), C::m_iValue
sẽ không được sửa đổi (đó là A::m_iValue
cha mẹ của C
).
Đây là nơi kế thừa ảo trở nên tiện dụng, vì với nó, bạn sẽ quay trở lại với bố cục kim cương thực sự, không chỉ với một foo()
phương thức, mà còn một và chỉ một m_iValue
.
Hãy tưởng tượng:
A
có một số tính năng cơ bản.B
thêm vào đó một số loại dữ liệu thú vị (ví dụ)C
thêm vào đó một số tính năng thú vị như mẫu quan sát viên (ví dụ: trên m_iValue
).D
kế thừa từ B
và C
, và do đó từ A
.Với thừa kế bình thường, sửa đổi m_iValue
từ D
là mơ hồ và điều này phải được giải quyết. Ngay cả nếu có, có hai m_iValues
bên trongD
, vì vậy bạn nên nhớ điều đó và cập nhật hai cái cùng một lúc.
Với kế thừa ảo, sửa đổi m_iValue
từ D
là ổn ... Nhưng ... Hãy nói rằng bạn có D
. Thông qua C
giao diện của nó , bạn đã đính kèm một người quan sát. Và thông qua B
giao diện của nó , bạn cập nhật mảng thú vị, có tác dụng phụ là thay đổi trực tiếpm_iValue
...
Vì việc thay đổi m_iValue
được thực hiện trực tiếp (không sử dụng phương thức truy cập ảo), người quan sát "nghe" thông qua C
sẽ không được gọi, bởi vì mã thực hiện việc nghe đang ở trong C
vàB
không biết về nó ...
Nếu bạn đang có một viên kim cương trong hệ thống phân cấp của mình, điều đó có nghĩa là bạn có xác suất 95% đã làm sai điều gì đó với hệ thống phân cấp nói trên.
Giải thích đa kế thừa với các cơ sở ảo đòi hỏi kiến thức về mô hình đối tượng C ++. Và giải thích chủ đề rõ ràng được thực hiện tốt nhất trong một bài viết chứ không phải trong một hộp bình luận.
Giải thích tốt nhất, dễ đọc nhất mà tôi thấy đã giải quyết được mọi nghi ngờ của tôi về chủ đề này là bài viết này: http://www.phpcompiler.org/articles/virtualinherribution.html
Bạn thực sự sẽ không cần phải đọc bất cứ điều gì khác về chủ đề này (trừ khi bạn là người viết trình biên dịch) sau khi đọc ...
Một lớp cơ sở ảo là một lớp không thể khởi tạo: bạn không thể tạo đối tượng trực tiếp từ nó.
Tôi nghĩ rằng bạn đang nhầm lẫn hai điều rất khác nhau. Kế thừa ảo không giống như một lớp trừu tượng. Kế thừa ảo sửa đổi hành vi của các cuộc gọi chức năng; đôi khi nó giải quyết các lệnh gọi hàm nếu không sẽ mơ hồ, đôi khi nó trì hoãn việc xử lý cuộc gọi hàm đến một lớp khác với lớp mà người ta mong đợi trong một kế thừa không ảo.
Tôi muốn thêm vào làm rõ loại của OJ.
Di sản ảo không đến mà không có giá. Giống như với tất cả những thứ ảo, bạn có được một hiệu suất thành công. Có một cách xung quanh hiệu suất hit này có thể kém thanh lịch.
Thay vì phá vỡ viên kim cương bằng cách tạo ra hầu như, bạn có thể thêm một lớp khác vào viên kim cương, để có được thứ gì đó như thế này:
B
/ \
D11 D12
| |
D21 D22
\ /
DD
Không có lớp nào kế thừa hầu như, tất cả đều kế thừa công khai. Các lớp D21 và D22 sau đó sẽ ẩn hàm ảo f () không rõ ràng đối với DD, có lẽ bằng cách khai báo hàm riêng. Mỗi người định nghĩa một hàm bao bọc, lần lượt là F1 () và f2 (), mỗi lớp gọi lớp cục bộ (riêng) f (), do đó giải quyết xung đột. Lớp DD gọi F1 () nếu nó muốn D11 :: f () và f2 () nếu nó muốn D12 :: f (). Nếu bạn xác định nội tuyến của trình bao bọc, có thể bạn sẽ nhận được khoảng không chi phí.
Tất nhiên, nếu bạn có thể thay đổi D11 và D12 thì bạn có thể thực hiện cùng một mẹo bên trong các lớp này, nhưng thường thì không phải vậy.
Ngoài những gì đã được nói về thừa kế nhiều và ảo, có một bài viết rất thú vị trên Tạp chí của Tiến sĩ Dobb: Nhiều quyền thừa kế được coi là hữu ích
Bạn đang có một chút bối rối. Tôi không biết nếu bạn trộn lẫn một số khái niệm.
Bạn không có lớp cơ sở ảo trong OP của mình. Bạn chỉ cần có một lớp cơ sở.
Bạn đã thừa kế ảo. Điều này thường được sử dụng trong nhiều kế thừa để nhiều lớp dẫn xuất sử dụng các thành viên của lớp cơ sở mà không cần sao chép chúng.
Một lớp cơ sở với một hàm ảo thuần túy sẽ không được khởi tạo. điều này đòi hỏi cú pháp mà Paul nhận được. Nó thường được sử dụng để các lớp dẫn xuất phải xác định các hàm đó.
Tôi không muốn giải thích thêm về điều này bởi vì tôi hoàn toàn không hiểu những gì bạn đang hỏi.
Nó có nghĩa là một cuộc gọi đến một chức năng ảo sẽ được chuyển tiếp đến lớp "đúng".
C ++ FAQ Lite FTW.
Nói tóm lại, nó thường được sử dụng trong các tình huống đa thừa kế, trong đó hệ thống phân cấp "kim cương" được hình thành. Kế thừa ảo sau đó sẽ phá vỡ sự mơ hồ được tạo trong lớp dưới cùng, khi bạn gọi hàm trong lớp đó và hàm cần được phân giải thành lớp D1 hoặc D2 bên trên lớp dưới cùng đó. Xem mục FAQ để biết sơ đồ và chi tiết.
Nó cũng được sử dụng trong phái đoàn chị em , một tính năng mạnh mẽ (mặc dù không dành cho người yếu tim). Xem Câu hỏi thường gặp này .
Đồng thời xem Mục 40 trong Hiệu quả C ++ phiên bản 3 (43 trong phiên bản 2).
Ví dụ sử dụng runnable kim cương
Ví dụ này cho thấy cách sử dụng một lớp cơ sở ảo trong kịch bản điển hình: để giải quyết sự kế thừa kim cương.
#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);
từ chức năng chính cho tôi một lỗi biên dịch: aDefault is not a member of A
sử dụng gcc 5.4.0. Nó giả sử để làm gì?
Các lớp ảo không giống như thừa kế ảo. Các lớp ảo bạn không thể khởi tạo, kế thừa ảo là một thứ hoàn toàn khác.
Wikipedia mô tả nó tốt hơn tôi có thể. http://en.wikipedia.org/wiki/Virtual_inherribution
Với thừa kế thừa kế phi kim không phải là kim cương cấp 3 điển hình, khi bạn khởi tạo một đối tượng có nguồn gốc mới nhất, mới được gọi và kích thước cần thiết cho đối tượng được trình biên dịch giải quyết từ loại lớp và chuyển sang mới.
mới có chữ ký:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
Và gọi cho malloc
, trả về con trỏ void
Điều này sau đó được chuyển đến hàm tạo của đối tượng dẫn xuất nhất, nó sẽ ngay lập tức gọi hàm tạo giữa và sau đó hàm tạo giữa sẽ gọi ngay hàm tạo. Cơ sở sau đó lưu trữ một con trỏ tới bảng ảo của nó khi bắt đầu đối tượng và sau đó là các thuộc tính của nó sau nó. Điều này sau đó quay trở lại hàm tạo giữa sẽ lưu con trỏ bảng ảo của nó tại cùng một vị trí và sau đó là các thuộc tính của nó sau các thuộc tính sẽ được lưu trữ bởi hàm tạo. Nó trả về cho hàm tạo có nguồn gốc nhất, lưu trữ một con trỏ tới bảng ảo của nó tại cùng một vị trí và sau đó là các thuộc tính của nó sau các thuộc tính sẽ được lưu trữ bởi hàm tạo giữa.
Bởi vì con trỏ bảng ảo bị ghi đè, con trỏ bảng ảo kết thúc luôn là một trong những lớp dẫn xuất nhất. Virtualness lan truyền tới lớp dẫn xuất nhất vì vậy nếu một hàm là ảo trong lớp trung gian, nó sẽ là ảo trong lớp dẫn xuất nhất nhưng không phải là lớp cơ sở. Nếu bạn đa hình hóa một thể hiện của lớp dẫn xuất nhất cho một con trỏ tới lớp cơ sở thì trình biên dịch sẽ không giải quyết điều này thành một cuộc gọi gián tiếp đến bảng ảo và thay vào đó sẽ gọi hàm trực tiếp A::function()
. Nếu một hàm là ảo đối với loại mà bạn đã truyền vào thì nó sẽ giải quyết một cuộc gọi vào bảng ảo sẽ luôn là cuộc gọi của lớp dẫn xuất nhất. Nếu nó không ảo cho kiểu đó thì nó sẽ chỉ gọiType::function()
và chuyển con trỏ đối tượng đến nó, chuyển sang Kiểu.
Thực tế khi tôi nói con trỏ tới bảng ảo của nó, nó thực sự luôn là 16 của bảng ảo.
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
không được yêu cầu một lần nữa trong các lớp có nguồn gốc nhiều hơn nếu nó là ảo trong một lớp có nguồn gốc ít hơn bởi vì nó lan truyền. Nhưng nó có thể được sử dụng để chỉ ra rằng hàm thực sự là một hàm ảo, mà không phải kiểm tra các lớp mà nó kế thừa các định nghĩa kiểu.
override
là một trình bảo vệ trình biên dịch khác nói rằng hàm này đang ghi đè lên một cái gì đó và nếu nó không bị lỗi trình biên dịch.
= 0
có nghĩa là đây là một chức năng trừu tượng
final
ngăn không cho một hàm ảo được thực hiện lại trong một lớp dẫn xuất hơn và sẽ đảm bảo rằng bảng ảo của lớp dẫn xuất nhất chứa hàm cuối cùng của lớp đó.
= default
làm cho nó rõ ràng trong tài liệu rằng trình biên dịch sẽ sử dụng thực hiện mặc định
= delete
đưa ra một lỗi biên dịch nếu một cuộc gọi đến đây được thử
Xem xét
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();
}
Nếu không kế thừa lớp bass, bạn sẽ có được một đối tượng trông như thế này:
Thay vì điều này:
Tức là sẽ có 2 đối tượng cơ sở.
Trong tình hình thừa kế kim cương ảo trên, sau khi mới được gọi, nó gọi các nhà xây dựng có nguồn gốc nhất và trong constructor đó, nó gọi tất cả 3 nhà xây dựng có nguồn gốc qua offsets vào bảng bảng ảo của nó, thay vì gọi chỉ gọi DerivedClass1::DerivedClass1()
và DerivedClass2::DerivedClass2()
và sau đó những người thực hiện cuộc gọiBase::Base()
Sau đây là tất cả được biên dịch trong chế độ gỡ lỗi -O0 vì vậy sẽ có lắp ráp dự phòng
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
Nó gọi Base::Base()
một con trỏ tới đối tượng bù 32. Base lưu trữ một con trỏ tới bảng ảo của nó tại địa chỉ mà nó nhận được và các thành viên của nó sau nó.
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()
sau đó gọi DerivedClass1::DerivedClass1()
bằng một con trỏ tới đối tượng offset 0 và cũng chuyển địa chỉ củaVTT 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()
sau đó chuyển địa chỉ của đối tượng + 16 và địa chỉ của VTT cho tổ DerivedDerivedClass+24
hợp DerivedClass2::DerivedClass2()
của nó giống hệt nhau DerivedClass1::DerivedClass1()
ngoại trừ dòng mov DWORD PTR [rax+8], 3
rõ ràng có 4 thay vì 3 chod = 4
.
Sau này, nó thay thế cả 3 con trỏ bảng ảo trong đối tượng bằng các con trỏ tới offset trong DerivedDerivedClass
vtable thành biểu diễn cho lớp đó.
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