多重继承:虚拟指针的类大小?

时间:2012-03-16 13:09:17

标签: c++ multiple-inheritance

鉴于代码:

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

输出: sizeof(D)8

每个类都包含自己的虚拟指针,而不包含任何基类, 那么,为什么类(D)的大小是8?

4 个答案:

答案 0 :(得分:3)

这取决于编译器的实现。我的编译器是Visual Stdio C ++ 2005。

这样的代码:

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

将输出

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

B类只有一个虚拟指针。所以sizeof(B)=4。 C级也是。

但D多重继承class Bclass C。编译不合并两个虚拟表。所以class D有两个虚拟指针指向每个虚拟表。

如果D只继承一个类而不是虚拟继承。它将合并它们的虚拟表。

答案 1 :(得分:2)

这取决于编译器的实现,因此您应该指定您正在使用的编译器。无论如何 D 派生自两个类,因此它包含指向 B C vtables 基类指针的指针< / em>(我不知道这个名字的好名字。)

要对此进行测试,您可以将指针声明为 B ,并指向 C 并指定 D 到基类指针。转储这些价值观,你会发现它们与众不同!

修改
使用Visual C ++ 10.0进行测试,32位。

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

输出,猜测,并不奇怪:

  • 基数:1个字节(好吧,这是一个惊喜,至少对我而言)。
  • Derived1:4个字节
  • ReallyDerived1:12个字节(由于多重继承,每个基类4个字节)
  • ReallyDerived2:8个字节(如猜测的那样)
  • ReallyDerived3:4个字节(路径中只有一个带有虚拟继承的基类,但这是非虚拟的)。

向基础添加虚拟方法,每个类可以获得4个字节。所以可能额外的字节不是vtable指针,而是多重继承中使用的基类指针,这种行为不会改变删除虚拟继承(但如果不是虚拟,则大小不会改变添加更多的基础)。

答案 2 :(得分:1)

首先:没有虚函数,很可能没有虚函数 课程中vptr。你看到的8个字节是一个神器 虚拟继承的实现方式。

层次结构中的多个类通常可以共享相同的内容 vptr。为了实现这一点,有必要将其偏移 final类是相同的,以及对于vtable条目的列表 基类是初始序列中的vtable条目列表 衍生类。

几乎所有单一实现都满足这两个条件 遗产。无论遗产有多深,通常都会有 只有一个vptr,在所有类之间共享。

在多重继承的情况下,总会有至少一个 因为两个基础,所以不满足这些要求的类 类不能有一个共同的起始地址,除非它们具有完全相同的地址 相同的虚函数,只有一个vtable可能是一个 另一个的初始序列。

虚拟继承增加了另一个怪癖,因为它的位置 相对于从中继承的类的虚拟基数会有所不同 取决于层次结构的其余部分。我见过的大多数实现 为此使用一个单独的指针,虽然应该可以放 这个信息也在vtable中。

如果我们采用您的层次结构,请添加虚拟函数 某些人vptr,我们注意到BD仍然可以分享 vtable,但AC都需要单独vtables。这意味着 如果您的类具有虚函数,则至少需要三个 vptr。 (由此我得出结论,您的实现正在使用 单独指向虚拟基地的指针。由BD分享 相同的指针,C有自己的指针。当然,A没有 有一个虚拟基础,并且不需要指向自身的指针。)

如果您正在尝试分析究竟发生了什么,我建议您添加 每个类中的新虚函数,并添加一个大小的指针 初始化的整数类型,每个类型具有不同的已知值 类。 (使用构造函数设置值。)然后创建一个实例 该类,取其地址,然后输出每个基地的地址 类。然后转储类:已知的固定值将有助于 确定不同元素所在的位置。类似的东西:

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() {}
};

您可能想要添加Derived2,其源自Derived并查看 在例如...之间的相对地址会发生什么LeftVB 取决于对象是否具有DerivedDerived2类型。

答案 3 :(得分:0)

你做了太多的假设。这在很大程度上取决于ABI,因此您应该查看适用于您平台的文档(我的猜测是您在32位平台上运行)。

首先,您的示例中没有虚函数,这意味着没有任何类型实际包含指向虚拟表的指针。那么这两个指针来自哪里? (我假设你使用的是32位架构)。那么,虚拟继承就是答案。当您虚拟继承时,虚拟基础(A)相对于派生类型(B,C)中的额外元素的相对位置将沿着继承链发生变化。在B或C对象的情况下,编译器可以将类型布置为[A,B']和[A,C'](其中X'是A中不存在的X的额外字段)。

现在虚拟继承意味着在D的情况下只有一个A子对象,因此编译器可以将D类型布局为[A,B',C',D]或[A,C',B' ,D](或任何其他组合,A可能位于对象的末尾,等等,这在ABI中定义)。那么这意味着什么,这意味着B和C的成员函数不能假设A子对象可能在哪里(在非虚拟继承的情况下,相对位置是已知的),因为完成类型实际上可能是链中的其他类型。

问题的解决方案是B和C通常都包含一个额外的指针到基地指针,类似但不等同于虚拟指针。与使用vptr动态调度到函数的方式相同,此额外指针用于动态查找基数。

如果您对所有这些细节感兴趣,我建议您阅读Itanium ABI,它不仅在Itanium中被广泛使用,而且在其他Intel 64架构(以及32个架构中的修改版本)中也被广泛使用编译器。