代码之家  ›  专栏  ›  技术社区  ›  Summer_More_More_Tea

关于虚拟继承层次结构的问题

  •  3
  • Summer_More_More_Tea  · 技术社区  · 16 年前

    顺便问一下,为什么以下代码的输出是:

    sizeof(A): 8
    sizeof(B): 20
    sizeof(C): 20
    sizeof(D): 36
    

    #include <iostream>
    
    using namespace std;
    
    class A{
        char k[ 3 ];
        public:
            virtual void a(){};
    };
    
    class B : public virtual A{
        char j[ 3 ];
        public:
            virtual  void b(){};
    };
    
    class C : public virtual A{
        char i[ 3 ];
        public:
            virtual void c(){};
    };
    
    class D : public B, public C{
        char h[ 3 ];
        public:
            virtual void d(){};
    };
    
    int main( int argc, char *argv[] ){
        cout << "sizeof(A): " << sizeof( A ) << endl;
        cout << "sizeof(B): " << sizeof( B ) << endl;
        cout << "sizeof(C): " << sizeof( C ) << endl;
        cout << "sizeof(D): " << sizeof( D ) << endl;
    
        return 0;
    }
    

    提前谢谢。 谨致问候。

    5 回复  |  直到 16 年前
        1
  •  5
  •   user33090    16 年前

    虚拟基对象位于属于该对象(大小为sizeof(object))的内存块中的某个位置。由于不同类型的多个子对象可以以不同的方式组合,但必须共享同一基本对象,因此每个子对象都需要一个偏移指针来查找虚拟基本对象。在没有虚拟继承的情况下,对于每个类类型,查找相应基对象的偏移量在编译时是固定的。

    sizeof值取决于编译器和计算机,但以下假设非常常见:

    假设:指针大小为4字节

    假设:类大小四舍五入到4字节的倍数

    sizeof(A): 8  ->   1 pointer to vtable (virtual method) 
                     + 3 chars -> 4+3=7 
                  -> round up to 8
    
    sizeof(B): 20 ->   8 + 1 pointer to vtable (virtual method) 
                     + 1 offset pointer to virtual base 
                     + 3 chars -> 8 + 4 + 4 + 3 = 19 
                  -> round up to 20
    
    sizeof(C): 32 ->  20 + 1 pointer to vtable (virtual method) 
                     + 1 offset pointer to virtual base 
                     + 3 chars 
                  -> 20 + 4 + 4 + 3 = 31 // this calculation refers to an older 
                  -> round up to 32      // version of the question's example 
                                         // where C had B as base class
    

    真实的 计算必须准确地知道编译器是如何工作的。

    奥利弗

    例子:

    class B  : virtual public A {...};
    class C  : virtual public A {...};
    class D1 : public B {...};
    class D2 : public B, C {...};
    

    A
    B
    D1
    

    D2可能的内存布局:

    A
    C
    B
    D2
    

    在第二种情况下,子对象B需要另一个偏移来找到它的基A

    D2类型的对象由一个内存块组成,其中包含所有父对象部分,即D2类型的对象的内存块具有a成员变量、C成员变量、B成员变量和D2成员变量的部分。这些部分的顺序依赖于编译器,但示例显示,对于多个虚拟继承,需要一个偏移指针,它指向 在内部 对象的总内存块到虚拟基对象。这是必要的,因为类B的方法只知道一个 指针。

    sizeof(D): 36 ->   A:3 chars + A:vtable 
                     + B:3 chars + B:vtable + B:virtual base pointer
                     + C:3 chars + C:vtable + C:virtual base pointer
                     + D:3 chars + D:vtable
                   =   3 + 4 
                     + 3 + 4 + 4 
                     + 3 + 4 + 4 
                     + 3 + 4 
                     = 36
    

    上述计算可能是错误的;—)。。。

    我不确定D部分是否有自己的vtable指针(这都高度依赖于编译器)。

    我现在认为,可能是D部分使用其父类的vtable指针条目,4个额外的字节用于对齐每个部分(8字节的倍数):

    所以这个计算可能更正确:

    sizeof(D): 36 ->   A:3 chars + A:vtable + A:alignment
                     + B:3 chars + B:vtable + B:virtual base pointer + B:alignment
                     + C:3 chars + C:vtable + C:virtual base pointer + C:alignment
                     + D:3 chars + D:alignment
                   =   3 + 4 + 1
                     + 3 + 4 + 4 + 1 
                     + 3 + 4 + 4 + 1
                     + 3 + 1
                     = 36
    
        2
  •  1
  •   kumar_m_kiran    16 年前

    我对上述问题有三点分析


    “虚拟继承是一种机制,通过这种机制,类指定它愿意共享其虚拟基类的状态。在虚拟继承下,对于给定的虚拟基,只继承一个共享基类子对象,而不管该类作为虚拟基在派生层次结构中出现多少次。共享基类子对象称为虚拟基类。“。。。来自利普曼

    虚拟继承只会避免从多重继承继承的重复子对象。但这并不表示基类对象将不是子对象。相反,即使在虚拟继承期间,子对象(至少会有一个副本-我的意思是将包含在sizeof()操作中)。


    虚函数用于动态绑定层次结构中所涉及对象的成员函数。所以即使这样,对子对象的排列也没有任何意义。


    继承函数的每个对象肯定包含子对象的空间。

        3
  •  1
  •   Dan    16 年前

    子类的对象是否直接持有其超类的对象

    如果您的层次结构是这样的,没有任何虚拟继承:

    #     W    <--- base class
    #    / \
    #   X   Y  <--- subclasses of W
    #    \ /
    #     Z    <--- most derived class
    

    那么 Z 会有两份 W . 但如果你能 X-->W Y-->W 那么,虚拟继承 Z 因为 Z 的两个超类共享它们的公共基类。

    #     W
    #    / \   <--- make these two virtual to eliminate duplicate W in Z.
    #   X   Y
    #    \ /
    #     Z
    

    class A{...};
    class B : public virtual A{...};
    class C : public virtual B{...}; // Edit: OP's code had this typo when I answered
    class D : public B, public C{...};
    

    让B实际上从A继承是没有必要的。你唯一需要的虚拟继承是 C-->B D-->B

    #   What you have     |     What you want?
    #             A       |               A
    #            /        |              /
    #           /v        |             /
    #          /          |            /
    #         B           |           B
    #        / \          |          / \
    #       /v  \         |         /v  \v
    #      /     \        |        /     \
    #     C       )       |       C       )
    #      \     /        |        \     /
    #       \   /         |         \   /
    #        \ /          |          \ /
    #         D           |           D
    

    当然,如果有其他类没有显示,它们继承自A和B,这会改变一些事情——也许 B-->A 如果你没有告诉我们另一颗钻石的话,继承权就必须是虚拟的。

        4
  •  0
  •   Billy ONeal IS4    16 年前


    这是不对的。几种实现将以这种方式进行,但它不是通过标准C++来定义的。标准C++没有指定如何实现这些东西。

    虚拟继承仅用于派生类从两个基类相乘继承的多重继承的某些情况,这些基类本身从公共基类继承。iostream库就是一个例子,其中istream和ostream继承自基本IO,而iostream继承自istream和ostream(因此一个iostream将有两个基本IO而没有虚拟继承)。

    虚拟继承呢?在这种情况下,子类的对象是直接持有其超类的对象,还是只持有指向其超类的对象的指针?
    这是实现定义的。你不需要知道,也不应该对此做任何假设。可以说,虚拟继承有一个运行时惩罚,这就是为什么在不需要虚拟继承时应该避免虚拟继承的原因。

        5
  •  0
  •   curiousguy    13 年前

    比较:

    struct A {
        void *vptr; // offset 0 size 4 alignment 4
        char k[3]; // offset 4 size 3 alignment 1
        char unnamed_padding; // offset 7 size 1
        // total size 8 alignment 4
    };
    
    // MS:
    struct base_B {
        void *vptr; // offset 0 size 4 alignment 4
        char j[3]; // offset 4 size 3 alignment 1
        char unnamed_padding; // offset 7 size 1
        A &a_subobject; // offset 8 size 4 alignment 4
        // total size 12 alignment 4
    
        base_B (&a_subobject) :a_subobject(a_subobject) {}
    };
    
    struct B {
        base_B b; // offset 0 size 12 alignment 4
        A a_subobject; // offset 12 size 8 alignment 4
        // total size 20 alignment 4
    
        B () : b(a_subobject) {}
    };
    
    struct base_C {
        void *vptr; // offset 0 size 4 alignment 4
        char i[3]; // offset 4 size 3 alignment 1
        char unnamed_padding; // offset 7 size 1
        A &a_subobject; // offset 8 size 4 alignment 4
        // total size 12 alignment 4
    
        base_C (&a_subobject) : a_subobject(a_subobject) {}
    };
    
    struct C {
        base_C c;
        A a_subobject; // offset 12 size 8 alignment 4
        // total size 20 alignment 4
    
        C () : c(a_subobject) {}
    };
    
    struct D {
        // no new vptr!
        // base_B is used as primary base: b_subobject.vptr is used as vptr
        base_B b_subobject; // offset 0 size 12 alignment 4
        base_C c_subobject; // offset 12 size 12 alignment 4
        char h[3];  // offset 24 size 3 alignment 1
        char unnamed_padding; // offset 27 size 1
        A a_subobject; // offset 28 size 8 alignment 4
        // total size 36 alignment 4
    
        D (): b_subobject(a_subobject), c_subobject(a_subobject) {}
    };
    
    // GCC:
    struct base_B {
        void *vptr; // offset 0 size 4 alignment 4
        char j[3]; // offset 4 size 3 alignment 1
        char unnamed_padding; // offset 7 size 1
        // total size 8 alignment 4
    };
    
    struct B {
        base_B b; // offset 0 size 12 alignment 4
        A a_subobject; // offset 8 size 8 alignment 4
        // total size 16 alignment 4
    };
    
    struct base_C {
        void *vptr; // offset 0 size 4 alignment 4
        char i[3]; // offset 4 size 3 alignment 1
        char unnamed_padding; // offset 7 size 1
        // total size 8 alignment 4
    };
    
    struct C {
        base_C b; // offset 0 size 12 alignment 4
        A a_subobject; // offset 8 size 8 alignment 4
        // total size 16 alignment 4
    };
    
    struct D {
        // no new vptr!
        // base_B is used as primary base: b_subobject.vptr is used as vptr
        base_B b_subobject; // offset 0 size 8 alignment 4
        base_C c_subobject; // offset 8 size 8 alignment 4
        char h[3];  // offset 16 size 3 alignment 1
        char unnamed_padding; // offset 19 size 1
        A a_subobject; // offset 20 size 8 alignment 4
        // total size 24 alignment 4
    };