2012-03-16 40 views
6

Với mã:Nhiều thừa kế: kích thước của lớp cho con trỏ ảo?

class A{}; 

class B : public virtual A{}; 

class C : public virtual A{}; 

class D : public B,public C{}; 

int main(){ 
cout<<"sizeof(D)"<<sizeof(D); 
return 0; 
} 

Output: sizeof (D) 8

Mỗi lớp chứa con trỏ ảo riêng của mình chỉ không của bất kỳ của lớp cơ sở của nó, Vì vậy, tại sao kích thước của lớp (D) là 8?

+0

Bạn cần phải nói gì biên dịch và kiến ​​trúc bạn đang ở trên. –

+0

Không có gì trong các cuộc đàm phán tiêu chuẩn về con trỏ ảo mà họ không thực sự tồn tại. Vì vậy, kích thước là 8 bởi vì trình biên dịch cần nó được 8. Đó là một chi tiết thực hiện và có rất ít điểm suy đoán về nó vì nó có thể khác nhau trên trình biên dịch khác. –

Trả lời

3

Nó phụ thuộc vào việc thực hiện biên dịch. trình biên dịch của tôi là Visual C++ stdio 2005.

Mã như thế này:

int main(){ 
    cout<<"sizeof(B):"<<sizeof(B) << endl; 
    cout<<"sizeof(C):"<<sizeof(C) << endl; 
    cout<<"sizeof(D):"<<sizeof(D) << endl; 
    return 0; 
} 

Nó ra

sizeof(B):4 
sizeof(C):4 
sizeof(D):8 

lớp B sẽ chỉ có một con trỏ ảo. Vì vậy, sizeof(B)=4. Và lớp C cũng vậy.

Nhưng D đa thừa kế class Bclass C. Biên dịch không hợp nhất hai bảng ảo. Vì vậy, class D có hai điểm con trỏ ảo cho mỗi bảng ảo.

Nếu D chỉ thừa kế một lớp và không thừa kế ảo. Nó sẽ hợp nhất chúng bảng ảo.

+1

Tôi đồng ý! Chúng tôi chỉ đoán vì nó là một chi tiết thực hiện của trình biên dịch (với phiên bản/kiến ​​trúc và nền tảng khác biệt của nó) nhưng nó là chính đáng. –

2

Nó phụ thuộc vào việc triển khai trình biên dịch, do đó bạn nên chỉ định trình biên dịch bạn đang sử dụng. Dù sao D xuất phát từ hai lớp nên nó có chứa các con trỏ tới BC vtables con trỏ lớp cơ sở (Tôi không biết một tên tốt cho việc này).

Để kiểm tra điều này, bạn có thể khai báo một con trỏ để B và một con trỏ đến C và đúc địa chỉ của D để con trỏ lớp cơ sở. Dump giá trị đó và bạn sẽ thấy chúng khác nhau!

EDIT
Thử nghiệm được thực hiện với Visual C++ 10.0, 32 bit.

class Base 
{ 
}; 

class Derived1 : public virtual Base 
{ 
}; 

class Derived2 : public virtual Base 
{ 
}; 

class Derived3 : public virtual Base 
{ 
}; 

class ReallyDerived1 : public Derived1, public Derived2, public Derived3 
{ 
}; 

class ReallyDerived2 : public Derived1, public Derived2 
{ 
}; 

class ReallyDerived3 : public Derived2 
{ 
}; 

void _tmain(int argc, _TCHAR* argv[]) 
{ 
std::cout << "Base: " << sizeof(Base) << std::endl; 
std::cout << "Derived1: " << sizeof(Derived1) << std::endl; 
std::cout << "ReallyDerived1: " << sizeof(ReallyDerived1) << std::endl; 
std::cout << "ReallyDerived2: " << sizeof(ReallyDerived2) << std::endl; 
std::cout << "ReallyDerived3: " << sizeof(ReallyDerived3) << std::endl; 
} 

Output, đoán, là không đáng ngạc nhiên:

  • Base: 1 byte (OK, đây là một bất ngờ, ít nhất là đối với tôi).
  • Derived1: 4 byte
  • ReallyDerived1: 12 byte (4 byte cho mỗi lớp cơ sở vì đa kế thừa)
  • ReallyDerived2: 8 byte (như đoán)
  • ReallyDerived3: 4 byte (chỉ là một lớp cơ sở với ảo thừa kế trong đường dẫn nhưng đây không phải là ảo).

Thêm phương thức ảo vào cơ sở bạn nhận được 4 byte cho mỗi lớp. Vì vậy, có thể thêm byte không phải là con trỏ vtable nhưng con trỏ lớp cơ sở được sử dụng trong nhiều thừa kế, hành vi này không thay đổi loại bỏ thừa kế ảo (nhưng nếu không ảo kích thước không thay đổi thêm nhiều cơ sở).

+1

Trong trường hợp này, tôi sẽ ngạc nhiên nếu có bất kỳ con trỏ nào đến 'vtables'. Nó sẽ yêu cầu ít nhất 3. Ngoại trừ rằng không có bất kỳ chức năng ảo, nó không yêu cầu bất kỳ. (Và không có cách nào để chuyển đổi một 'A * 'để một' B *'.) –

+0

tôi ** ** đoán (nếu ai đó biết xin vui lòng chính xác cho tôi!) Trình biên dịch tạo ra một con trỏ tới lớp cơ sở thậm chí không có phương pháp ảo vì đa kế thừa (vì nó là cần thiết cho downcasts). Chỉ nên có HAI con trỏ tới lớp cơ sở vtable, một cho lớp cơ sở và không có gì cho ** C ** vì không có phương thức ảo.
Ý tôi là: khai báo một con trỏ B * pB và một con trỏ khác là C * pC. Sau đó downcast một con trỏ đến D và gán nó cho pB và pC. :) –

+0

Tôi đang sử dụng trình biên dịch g ++ – Luv

1

Đầu tiên: không có chức năng ảo, có thể không có vptr ở tất cả các lớp. 8 byte bạn đang thấy là một tạo tác cách thức thừa kế ảo được triển khai.

Thường có thể có một số lớp trong một cấu trúc phân cấp để chia sẻ cùng một vptr. Để điều này xảy ra, cần phải bù đắp chúng trong lớp cuối cùng là và danh sách các mục nhập vtable trong lớp cơ sở là một chuỗi ban đầu danh sách các mục nhập vtable trong lớp dẫn xuất .

Cả hai điều kiện được đáp ứng trong hầu như tất cả các triển khai cho đơn thừa kế .Bất kể thừa kế sâu bao nhiêu, thường chỉ có chỉ một số vptr, được chia sẻ giữa tất cả các lớp.

Trong trường hợp của đa kế thừa, sẽ luôn có ít nhất một lớp mà các yêu cầu này không được đáp ứng, kể từ khi hai cơ sở lớp không thể có một địa chỉ bắt đầu phổ biến, và trừ khi họ có chính xác các chức năng ảo giống nhau, chỉ một vtable của một người có thể có thể là một chuỗi ban đầu của một chuỗi khác là .

Thừa kế ảo thêm một dấu nháy khác, vì vị trí của cơ sở ảo liên quan đến lớp kế thừa từ nó sẽ thay đổi tùy thuộc vào phần còn lại của cấu trúc phân cấp. Hầu hết các triển khai tôi đã thấy sử dụng một con trỏ riêng biệt cho điều này, mặc dù nó có thể được đặt thông tin này trong vtable là tốt.

Nếu chúng ta lấy hệ thống cấp bậc của bạn, thêm chức năng ảo để chúng ta có nhất định của việc có một vptr, chúng tôi nhận thấy rằng BD vẫn có thể chia sẻ một vtable, nhưng cả hai AC cần riêng vtables. Điều này có nghĩa là nếu các lớp học của bạn có chức năng ảo, bạn sẽ cần ít nhất ba vptr. (Từ đây tôi kết luận rằng việc triển khai của bạn đang sử dụng con trỏ riêng biệt tới cơ sở ảo. Với BD chia sẻ số cùng một con trỏ và C với con trỏ riêng của nó Và tất nhiên, A không không cần một con trỏ đến chính nó.)

Nếu bạn đang cố gắng để phân tích chính xác những gì đang xảy ra, tôi muốn đề nghị thêm một hàm ảo mới trong mỗi lớp, và thêm một con trỏ có kích thước loại không thể thiếu đó ban đầu bạn có một giá trị đã biết khác nhau cho mỗi lớp . (Sử dụng các hàm tạo để thiết lập giá trị.) Sau đó tạo một cá thể của lớp , lấy địa chỉ của nó, sau đó xuất địa chỉ cho mỗi lớp cơ sở . Và sau đó đổ lớp: các giá trị cố định đã biết sẽ giúp đỡ trong xác định vị trí của các phần tử khác nhau. Một cái gì đó như:

struct VB 
{ 
    int vb; 
    VB() : vb(1) {} 
    virtual ~VB() {} 
    virtual void fvb() {} 
}; 

struct Left : virtual VB 
{ 
    int left; 
    Left() : left(2) {} 
    virtual ~Left() {} 
    virtual void fvb() {} 
    virtual void fleft() {} 
}; 

struct Right : virtual VB 
{ 
    int right; 
    Right() : right(3) {} 
    virtual ~Right() {} 
    virtual void fvb() {} 
    virtual void fright() {} 
}; 

struct Derived : Left, Right 
{ 
    int derived; 
    Derived() : derived(5) {} 
    virtual ~Derived() {} 
    virtual void fvb() {} 
    virtual void fleft() {} 
    virtual void fright() {} 
    virtual void fderived() {} 
}; 

Bạn có thể muốn thêm một Derived2, mà xuất phát từ Derived và xem gì xảy ra với các địa chỉ tương đối giữa ví dụ LeftVB tùy thuộc vào việc đối tượng có loại Derived hoặc Derived2.

+0

Kiểm tra với hơn 2 lớp cơ sở, tôi nghĩ nó không phải vì các phương thức ảo mà bởi vì con trỏ tới lớp cơ sở "vtable" (hiện tại nếu không tồn tại) –

+0

Nếu không có hàm ảo nào, 'vtable' không cần thiết, nhưng một số loại con trỏ tới lớp cơ sở ảo là (vì chúng cần thiết để chuyển đổi một 'Derived *' thành một 'VB *'). –

0

Bạn đang tạo quá nhiều giả định. Điều này phụ thuộc nhiều vào ABI, vì vậy bạn nên xem xét tài liệu cho nền tảng của bạn (tôi đoán là bạn đang chạy trên nền tảng 32 bit).

Điều đầu tiên là không có chức năng ảo trong ví dụ của bạn và điều đó có nghĩa là không có loại nào thực sự chứa con trỏ tới bảng ảo. Vậy hai con trỏ đó đến từ đâu? (Tôi giả sử bạn đang ở trên kiến ​​trúc 32 bit). Vâng, thừa kế ảo là câu trả lời. Khi bạn thừa hưởng hầu như, vị trí tương đối của cơ sở ảo (A) đối với các phần tử phụ trong kiểu dẫn xuất (B, C) sẽ thay đổi theo chuỗi thừa kế. Trong trường hợp đối tượng B hoặc C, trình biên dịch có thể đặt các loại như [A, B '] và [A, C'] (trong đó X 'là các trường thừa của X không có trong A).

Bây giờ thừa kế ảo có nghĩa là chỉ có một đối tượng con trong trường hợp D, do đó trình biên dịch có thể bố trí kiểu D là [A, B ', C', D] hoặc [A, C ', B ', D] (hoặc bất kỳ kết hợp nào khác, A có thể ở cuối đối tượng, vv, điều này được định nghĩa trong ABI). Vì vậy, điều này có nghĩa là, điều này ngụ ý rằng chức năng thành viên của B và C không thể giả định nơi A subobject có thể (trong trường hợp thừa kế không ảo, vị trí tương đối nổi tiếng), bởi vì loại hoàn thực sự có thể là một số loại khác xuống chuỗi.

Giải pháp cho vấn đề là cả B và C thường chứa một con trỏ con trỏ đến cơ sở khác, tương tự nhưng không tương đương với con trỏ ảo. Trong cùng một cách mà vptr được sử dụng để tự động gửi đến một hàm, con trỏ thêm này được sử dụng để tự động tìm cơ sở.

Nếu bạn quan tâm đến tất cả các chi tiết này, tôi khuyên bạn nên đọc Itanium ABI, được sử dụng rộng rãi không chỉ trong Itanium mà còn trong các kiến ​​trúc khác của Intel 64 (và phiên bản sửa đổi trong 32 kiến ​​trúc).

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