Trong C ++, lớp cơ sở ảo là gì?


403

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() { /* ... */ }
};

chúng ta có nên sử dụng các lớp cơ sở ảo trong 'nhiều thừa kế' bởi vì nếu lớp A có biến thành viên int a và lớp B cũng có thành viên int a và lớp c kế thừa lớp A và B, làm thế nào để chúng ta quyết định sử dụng 'a' nào?
Namit Sinha

2
@NamitSinha không, thừa kế ảo không giải quyết được vấn đề đó. Thành viên a dù sao cũng sẽ mơ hồ
Ichthyo

@NamitSinha Di sản ảo không phải là một công cụ kỳ diệu để loại bỏ nhiều sự mơ hồ liên quan đến thừa kế. Nó "giải quyết" một "vấn đề" của việc có một cơ sở gián tiếp hơn một lần. Đó chỉ là một vấn đề nếu nó được dự định chia sẻ (thường nhưng không phải luôn luôn như vậy).
tò mò

Câu trả lời:


533

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àycái này . Một ví dụ tốt cũng có sẵn ở đây .


7
@Bohdan không có nó :)
OJ.

6
@OJ. tại sao không? Họ rất vui nhộn :)
Bohdan

15
@Bohdan sử dụng từ khóa ảo càng ít, bởi vì khi chúng ta sử dụng từ khóa ảo, một cơ chế trọng lượng nặng được áp dụng. Vì vậy, hiệu quả chương trình của bạn sẽ bị giảm.
Sagar

73
Sơ đồ "kim cương đáng sợ" của bạn là khó hiểu, mặc dù nó dường như được sử dụng phổ biến. Đây thực sự là một sơ đồ hiển thị các mối quan hệ kế thừa lớp - không phải là một bố cục đối tượng. Phần khó hiểu là nếu chúng ta sử dụng 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 virtualthì bố cục đối tượng trông giống như một cấu trúc cây chứa hai As
MM

5
Tôi phải downvote câu trả lời này vì lý do được nêu ra bởi MM - sơ đồ thể hiện ngược lại với bài viết.
David Stone

251

Về cách bố trí bộ nhớ

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_iValuetừ 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_iValuechứ không phải một. Và nếu bạn sửa đổi một, giả sử, B::m_iValue(đó là A::m_iValuecha mẹ của B), C::m_iValuesẽ không được sửa đổi (đó là A::m_iValuecha 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.

Điều gì có thể đi sai?

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ụ)
  • Cthê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).
  • Dkế thừa từ BC, và do đó từ A.

Với thừa kế bình thường, sửa đổi m_iValuetừ Dlà mơ hồ và điều này phải được giải quyết. Ngay cả nếu có, có hai m_iValuesbê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_iValuetừ Dlà ổn ... Nhưng ... Hãy nói rằng bạn có D. Thông qua Cgiao diện của nó , bạn đã đính kèm một người quan sát. Và thông qua Bgiao 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 Csẽ không được gọi, bởi vì mã thực hiện việc nghe đang ở trong CB không biết về nó ...

Phần kết luậ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.


'Điều gì có thể sai' của bạn là do truy cập trực tiếp vào một thành viên cơ sở, không phải do nhiều kế thừa. Loại bỏ 'B "và bạn có cùng một vấn đề. Quy tắc cơ bản của:' nếu nó không riêng tư thì nó phải là ảo 'tránh được vấn đề. M_iValue không phải là ảo và nên ở chế độ riêng tư
Chris Dodd

4
@Chris Dodd: Không chính xác. Điều gì xảy ra với m_iValue sẽ xảy ra với bất kỳ ký hiệu nào ( ví dụ typedef, biến thành viên, hàm thành viên, chuyển sang lớp cơ sở, v.v. ). Đây thực sự là một vấn đề đa kế thừa, một vấn đề mà người dùng nên lưu ý để sử dụng nhiều kế thừa một cách chính xác, thay vì đi theo cách của Java và kết luận "Đa kế thừa là xấu 100%, hãy làm điều đó với các giao diện".
paercebal

Xin chào, Khi chúng tôi sử dụng từ khóa ảo, sẽ chỉ có một bản sao của A. Câu hỏi của tôi là làm thế nào để chúng tôi biết liệu nó đến từ B hoặc C? Câu hỏi của tôi có hợp lệ không?
dùng875036

@ user875036: A đến từ cả B và C. Thật vậy, ảo thay đổi một số thứ (ví dụ: D sẽ gọi hàm tạo của A, không phải B, cũng không phải C). Cả B và C (và D) đều có một con trỏ tới A.
paercebal

3
FWIW, trong trường hợp ai đó thắc mắc, các biến thành viên không thể là ảo - ảo là một chỉ định cho các hàm . Tài liệu tham khảo SO: stackoverflow.com/questions/3698831/ từ
rholmes

34

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 ...


10

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.


7

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.


2
Đây không phải là vấn đề ít nhiều thanh lịch hoặc giải quyết sự mơ hồ (bạn luôn có thể sử dụng thông số kỹ thuật xxx :: rõ ràng cho điều đó). Với thừa kế không ảo, mọi phiên bản của lớp DD đều có hai trường hợp độc lập của B. Ngay khi lớp có một thành viên dữ liệu không tĩnh, kế thừa ảo và không ảo khác nhau nhiều hơn chỉ là cú pháp.
dùng3489112

@ user3489112 Ngay khi ... không có gì. Kế thừa ảo và không ảo khác nhau về ngữ nghĩa, thời kỳ.
tò mò


1

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.


1
Một "lớp cơ sở" được sử dụng trong thừa kế ảo sẽ trở thành "lớp cơ sở ảo" (trong bối cảnh của sự kế thừa chính xác đó).
Luc Hermitte

1

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).


1

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);
}

2
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 Asử dụng gcc 5.4.0. Nó giả sử để làm gì?
SebNag

@SebTu ah cảm ơn, chỉ cần một cái gì đó tôi quên xóa khỏi bản sao dán, loại bỏ nó ngay bây giờ. Ví dụ vẫn nên có ý nghĩa mà không có nó.
Ciro Santilli 郝海东 冠状 病 事件

0

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


6
Không có thứ gọi là "lớp ảo" trong C ++. Tuy nhiên, có "các lớp cơ sở ảo" là "ảo" liên quan đến một thừa kế nhất định. Những gì bạn giới thiệu là những gì chính thức được gọi là "lớp trừu tượng".
Luc Hermitte

@LucHermitte, chắc chắn có các lớp ảo trong C ++. Kiểm tra điều này: vi.wikipedia.org/wiki/Virtual_group .
Rafid

"lỗi: 'ảo' chỉ có thể được chỉ định cho các chức năng". Tôi không biết đây là ngôn ngữ gì. Nhưng chắc chắn không có thứ gọi là lớp ảo trong C ++.
Luc Hermitte

0

Kế thừa thường xuyên

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

virtualkhô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ử

Kế thừa ảo

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()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+24hợp DerivedClass2::DerivedClass2()của nó giống hệt nhau DerivedClass1::DerivedClass1()ngoại trừ dòng mov DWORD PTR [rax+8], 3rõ 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 DerivedDerivedClassvtable 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.