关于Virtual Inheritance层次结构的问题

时间:2010-05-02 16:47:03

标签: c++ virtual-inheritance

在处理虚拟继承时遇到此问题。我记得在非虚拟继承层次结构中,子类的对象拥有其直接超类的对象。那么虚拟继承呢?在这种情况下,子类的对象是直接持有其超类的对象还是仅仅持有一个指向其超类对象的指针?

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

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 个答案:

答案 0 :(得分:5)

虚拟基础对象位于内存块中属于对象的某个位置(内存大小= 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 {...};

D1的可能内存布局:

A
B
D1

D2的可能内存布局:

A
C
B
D2

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

D2类型的对象由一个内存块组成,其中包含所有父对象部分,即D2类型对象的内存块有一个A成员变量的部分,C成员变量,B成员变量和D2成员变量。这些部分的顺序取决于编译器,但是示例显示,对于多个虚拟继承,需要一个偏移指针,它将指向对象的总内存块到虚拟基础对象。这是必需的,因为B类的方法只知道一个这个指向B的指针,并且必须以某种方式计算A存储器部分相对于 this 指针的位置。

计算尺寸(D):

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

答案 1 :(得分:1)

我看到上述问题的三点分析

一个。虚拟继承

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

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

湾虚函数

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

℃。子对象的实现

这完全取决于编译器,并且由于所有原因很难确定 - 在其实现中。但是,我们可以确认对象的sizeof()也包括基类(子)对象的大小 - 我们可以将它们可视化为嵌入基类对象。

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

HTH

答案 2 :(得分:1)

  

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

是的,无论继承是否为虚拟,它都是如何工作的。我会用“包含”和“保持”这个词。

如果你的层次结构看起来像这样,在任何地方都没有虚拟遗产:

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

然后Z会有两份W。但是,如果您将X-->WY-->W遗传设为虚拟,则Z只会有W的一个副本,因为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-->BD-->B,因为这是钻石“合并”继承层次结构的位置:

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

当然,如果你没有显示继承自A和B的其他类,那会改变一些事情 - 如果你没有告诉我们另一个钻石,那么B-->A继承确实需要是虚拟的约。

答案 3 :(得分:0)

我记得在非虚拟继承层次结构中,子类的对象拥有其直接超类的对象。
这是不正确的。有几种实现方式会这样做,但标准C ++没有这样定义。标准C ++没有指定如何实现这些内容。

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

除非您处于此特定情况,否则不应使用虚拟继承。

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

答案 4 :(得分:0)

比较

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