Search code examples
c++objectinheritancemodelvirtual

Why virtual inherit 2 classes raises object size?


I've got a simple object, compile and run under 64bit ubuntu1804 with g++:

struct Base1{ int mi,mj,mk,mh;};
struct Base2{ int ni,nj,nk,nh;};

struct Child1:virtual Base1{virtual void f(){}};
struct Child2:virtual Base1{virtual void f(){}};
struct Derive1:Child1,Child2{};

struct Child3:virtual Base2{virtual void f(){}};
struct Child4:virtual Base2{virtual void f(){}};
struct Derive2:Child3,Child4{};

struct Final:Derive1,Derive2{};
int main(){
        cout<<"C1="<<sizeof(Child1)<<endl;
        cout<<"C2="<<sizeof(Child2)<<endl;
        cout<<"C3="<<sizeof(Child3)<<endl;
        cout<<"C4="<<sizeof(Child4)<<endl;
        cout<<"D1="<<sizeof(Derive1)<<endl;
        cout<<"D2="<<sizeof(Derive2)<<endl;
        cout<<"F ="<<sizeof(Final)<<endl;
        return 0;
}

The program outputs:

$ g++ om.cpp -O2 && ./a.out
C1=24
C2=24
C3=24
C4=24
D1=32
D2=32
F =64

I know that sizeof(B1) is 16, and Child1-Child4 as adding virtual function(vptr pointing to vtable) will add an extra pointer size, so they're of size 24, no problem. But why sizeof Derive1/Derive2 are 32? The c++ object model adds an extra pointer to it, right? But what doesn this extra pointer actually do, and why it's necessary to add this extra 8byte pointer? I don't see any necessity here.

Thanks a lot.


Solution

  • A plausible layout:

    Final
    ----------------------
    | Derive1
    | --------------------
    | | Child1
    | | ------------------
    | | | Pointer to Base1 (8 bytes)
    | | ------------------
    | | Child2
    | | ------------------
    | | | Pointer to Base1 (8 bytes)
    | | ------------------
    | --------------------
    | Derive2
    | --------------------
    | | Child3
    | | ------------------
    | | | Pointer to Base2 (8 bytes)
    | | ------------------
    | | Child4
    | | ------------------
    | | | Pointer to Base2 (8 bytes)
    | | ------------------
    | --------------------
    | Base1
    | --------------------
    | | mi                 (4 bytes)
    | | mj                 (4 bytes)
    | | mk                 (4 bytes)
    | | mh                 (4 bytes)
    | --------------------
    | Base2
    | --------------------
    | | ni                 (4 bytes)
    | | nj                 (4 bytes)
    | | nk                 (4 bytes)
    | | nh                 (4 bytes)
    | --------------------
    ----------------------
    

    Total size: 8 + 8 + 8 + 8 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 = 64
    Note that this size could grow to accommodate vtable pointers if your virtual functions were less trivial and/or actually overrode something. (As it stands, the virtual functions can be optimized completely away.)

    To understand why all these pointers are necessary, consider the following:

    Final foo;
    Child3 * c3 = &foo;
    Child4 * c4 = &foo;
    Base2 * b23 = c3;
    Base2 * b24 = c4;
    

    If you were given c4, how would you convert it to a pointer to Base2? Keep in mind that you are not allowed to assume that c4 points to part of Final; your solution must also work for the following, and it must be equally applicable to c3.

    Child4 c4;
    Base2 * b24 = &c4;