2015-06-10 20 views
7

Tôi đang đọc bài viết này "Virtual method table"bảng phương thức ảo cho nhiều thừa kế

Ví dụ trong bài viết ở trên:

class B1 { 
public: 
    void f0() {} 
    virtual void f1() {} 
    int int_in_b1; 
}; 

class B2 { 
public: 
    virtual void f2() {} 
    int int_in_b2; 
}; 

class D : public B1, public B2 { 
public: 
    void d() {} 
    void f2() {} // override B2::f2() 
    int int_in_d; 
}; 

B2 *b2 = new B2(); 
D *d = new D(); 

Trong bài viết này, tác giả giới thiệu rằng việc bố trí bộ nhớ của đối tượng d như sau:

  d: 
D* d-->  +0: pointer to virtual method table of D (for B1) 
      +4: value of int_in_b1 
B2* b2--> +8: pointer to virtual method table of D (for B2) 
      +12: value of int_in_b2 
      +16: value of int_in_d 

Total size: 20 Bytes. 

virtual method table of D (for B1): 
    +0: B1::f1() // B1::f1() is not overridden 

virtual method table of D (for B2): 
    +0: D::f2() // B2::f2() is overridden by D::f2() 

Câu hỏi là về d->f2(). Các cuộc gọi đến d->f2() qua một con trỏ B2 như một con trỏ this vì vậy chúng tôi phải làm một cái gì đó như:

(*(*(d[+8]/*pointer to virtual method table of D (for B2)*/)[0]))(d+8) /* Call d->f2() */ 

Tại sao chúng ta phải vượt qua một con trỏ B2 như con trỏ this không phải là gốc D con trỏ ??? Chúng tôi đang thực sự gọi D :: f2(). Dựa trên sự hiểu biết của tôi, chúng ta nên vượt qua một con trỏ D như this đến D :: f2) chức năng (.

___update____

Nếu đi qua một con trỏ B2 như this đến D :: f2(), gì nếu chúng ta muốn truy cập các thành viên của B1 lớp trong D :: f2() ?? Tôi tin rằng con trỏ B2 (this) được hiển thị như thế này:

  d: 
D* d-->  +0: pointer to virtual method table of D (for B1) 
      +4: value of int_in_b1 
B2* b2--> +8: pointer to virtual method table of D (for B2) 
      +12: value of int_in_b2 
      +16: value of int_in_d 

Nó đã có một số offset của địa chỉ bắt đầu của cách bố trí bộ nhớ tiếp giáp này. Ví dụ, chúng tôi muốn truy cập b1 bên trong D :: f2(), tôi đoán trong thời gian chạy, nó sẽ làm một cái gì đó như: *(this+4) (this trỏ đến cùng một địa chỉ như b2) mà sẽ chỉ b2 trong B ????

Trả lời

4

Chúng tôi không thể chuyển con trỏ D tới hàm ảo ghi đè B2::f2(), bởi vì tất cả ghi đè của cùng một chức năng ảo phải chấp nhận bố cục bộ nhớ giống nhau.

Kể từ B2::f2() chức năng dự kiến ​​bố trí bộ nhớ B2 's của đối tượng được truyền cho nó như con trỏ this của nó, ví dụ:

b2: 
    +0: pointer to virtual method table of B2 
    +4: value of int_in_b2 

các trọng chức năng D::f2() phải mong đợi bố trí giống nhau là tốt. Nếu không, các chức năng sẽ không còn có thể hoán đổi cho nhau nữa.

Để xem tại sao vấn đề thay thế lẫn nhau xem xét kịch bản này:

class B2 { 
public: 
    void test() { f2(); } 
    virtual void f2() {} 
    int int_in_b2; 
}; 
... 
B2 b2; 
b2.test(); // Scenario 1 
D d; 
d.test(); // Scenario 2 

B2::test() nhu cầu để thực hiện cuộc gọi của f2() trong cả hai kịch bản. Nó không có thêm thông tin để nói với nó như thế nào this con trỏ đã được điều chỉnh khi thực hiện các cuộc gọi *. Đó là lý do tại sao các trình biên dịch chuyển con trỏ cố định lên, vì vậy test() 's gọi của f2 sẽ làm việc cả với D::f2()B2::f2().

* Các triển khai khác có thể truyền đạt thông tin này rất tốt; tuy nhiên, nhiều triển khai thừa kế được thảo luận trong bài viết không làm điều đó.

+0

(1) ý bạn là gì bởi "hoán đổi cho nhau", bạn có thể giải thích chi tiết hơn một chút không? (2) Nếu chuyển một con trỏ 'B2' thành' this' tới D :: f2(), thì nếu chúng ta muốn truy cập các thành viên của lớp B1 trong D :: f2() thì sao? Đối với (2), vui lòng xem nội dung cập nhật của câu hỏi. – Fihop

+0

Cảm ơn rất nhiều !! Vui lòng xác minh tôi. 'B2 b2; b2.test() ', đó là một con trỏ' B2' được chuyển thành 'this' thành' B2 :: test() 'vì' b2' là một đối tượng độc lập. Đối với 'D d; d.test() ', trình biên dịch truyền con trỏ sửa lỗi thực sự trỏ đến đối tượng con' B2' của 'D' như' this' to 'test()' vì hàm gọi thực sự là 'B2 :: test() '. Nếu 'this' không trỏ đến' B2' bên trong 'D', nó sẽ gây ra vấn đề khi truy cập vào các thành viên của' B2' bên trong hàm 'B2 :: test()'. Đây là lý do tại sao tôi nghĩ rằng chúng ta nên vượt qua một con trỏ sửa chữa như 'this'. Ví dụ này không thể giải thích bản cập nhật. Cảm ơn vẫn còn – Fihop

+0

Đối với 'D d; d.test() ', tôi đồng ý chúng ta làm một cái gì đó giống như' d.test (B2 * b2) '(có nghĩa là' this' trỏ đến phụ đối tượng 'B2' của D). Tuy nhiên, bên trong 'B2: test',' b2-> f2() 'nên thực thi' D :: f2'. Tôi có đúng không? Bây giờ vấn đề trở thành loại 'này' được chuyển tới' b2-> f2() ' – Fihop

1

Với cấp bậc lớp học của bạn, một đối tượng thuộc loại B2 sẽ có dấu chân bộ nhớ sau.

+------------------------+ 
| pointer for B2 vtable | 
+------------------------+ 
| int_in_b2    | 
+------------------------+ 

Một đối tượng của loại D sẽ có bộ nhớ sau.

+------------------------+ 
| pointer for B1 vtable | 
+------------------------+ 
| int_in_b1    | 
+------------------------+ 
| pointer for B2 vtable | 
+------------------------+ 
| int_in_b2    | 
+------------------------+ 
| int_in_d    | 
+------------------------+ 

Khi bạn sử dụng:

D* d = new D(); 
d->f2(); 

gọi Đó là giống như:

B2* b = new D(); 
b->f2(); 

f2() có thể được gọi bằng một con trỏ kiểu B2 hoặc con trỏ kiểu D. Cho rằng thời gian chạy phải có khả năng làm việc chính xác với một con trỏ kiểu B2, nó phải có khả năng chuyển chính xác cuộc gọi đến D::f2() bằng cách sử dụng con trỏ hàm thích hợp trong vtable của B2. Tuy nhiên, khi cuộc gọi được gửi đến D:f2() thì con trỏ ban đầu của loại B2 phải bằng cách nào đó được bù đắp đúng cách để trong D::f2(), this trỏ đến một D, không phải là B2.

Đây là mã ví dụ của bạn, thay đổi một chút để in giá trị con trỏ hữu ích và dữ liệu thành viên để giúp hiểu những thay đổi về giá trị this trong các chức năng khác nhau.

#include <iostream> 

struct B1 
{ 
    void f0() {} 
    virtual void f1() {} 
    int int_in_b1; 
}; 

struct B2 
{ 
    B2() : int_in_b2(20) {} 
    void test_f2() 
    { 
     std::cout << "In B::test_f2(), B*: " << (void*)this << std::endl; 
     this->f2(); 
    } 

    virtual void f2() 
    { 
     std::cout 
     << "In B::f2(), B*: " << (void*)this 
     << ", int_in_b2: " << int_in_b2 << std::endl; 
    } 

    int int_in_b2; 
}; 

struct D : B1, B2 
{ 
    D() : int_in_d(30) {} 
    void d() {} 
    void f2() 
    { 
     // ====================================================== 
     // If "this" is not adjusted properly to point to the D 
     // object, accessing int_in_d will lead to undefined 
     // behavior. 
     // ====================================================== 

     std::cout 
     << "In D::f2(), D*: " << (void*)this 
     << ", int_in_d: " << int_in_d << std::endl; 
    } 
    int int_in_d; 
}; 

int main() 
{ 
    std::cout << "sizeof(void*) : " << sizeof(void*) << std::endl; 
    std::cout << "sizeof(int) : " << sizeof(int) << std::endl; 
    std::cout << "sizeof(B1) : " << sizeof(B1) << std::endl; 
    std::cout << "sizeof(B2) : " << sizeof(B2) << std::endl; 
    std::cout << "sizeof(D)  : " << sizeof(D) << std::endl << std::endl; 

    B2 *b2 = new B2(); 
    D *d = new D(); 
    b2->test_f2(); 
    d->test_f2(); 
    return 0; 
} 

Output của chương trình:

sizeof(void*) : 8 
sizeof(int) : 4 
sizeof(B1) : 16 
sizeof(B2) : 16 
sizeof(D)  : 32 

In B::test_f2(), B*: 0x1f50010 
In B::f2(), B*: 0x1f50010, int_in_b2: 20 
In B::test_f2(), B*: 0x1f50040 
In D::f2(), D*: 0x1f50030, int_in_d: 30 

Khi các đối tượng thực tế sử dụng để gọi test_f2()D, giá trị của this thay đổi từ 0x1f50040 trong test_f2() để 0x1f50030 trong D::f2(). So khớp với sizeof B1, B2D. Độ lệch của đối tượng phụ B2 của đối tượng D16 (0x10). Giá trị của this trong B::test_f2(), một B*, được thay đổi bởi 0x10 trước khi cuộc gọi được gửi đến D::f2().

Tôi sẽ đoán giá trị của khoản bù trừ từ D đến B2 được lưu trữ trong v.v. B2. Nếu không, không có cách nào một cơ chế gửi hàm chung có thể thay đổi giá trị this đúng trước khi gửi cuộc gọi đến đúng chức năng ảo.

Các vấn đề liên quan