2012-03-16 57 views
6

给予代码:多继承:虚拟指针的类的大小?

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?

+0

你需要说明你的编译器和体系结构。 – 2012-03-16 13:25:28

+0

标准中没有任何内容谈论虚拟指针,它们确实存在。所以大小是8,因为编译器需要它是8.它是一个实现细节,没有什么值得推测的,因为它可能在另一个编译器上不同。 – 2012-03-16 14:11:54

回答

3

这取决于编译器的实现。我的编译器是可视化的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类只有一个虚拟指针。所以​​。而C类也是。

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

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

+1

我同意!我们只是猜测,因为它是编译器的实现细节(其版本/体系结构和平台差异),但它是合理的。 – 2012-03-16 13:59:32

2

它取决于编译器的实现,所以你应该指定你正在使用的编译器。反正d从两个类派生所以它包含指向ç 虚函数表 基类指针(我不知道这个好名字)。

为了测试这一点,你可以宣布指针和指向Ç和投d的地址基类指针。转储的价值,你会看到他们不同!

EDIT
试验用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个字节(OK,这是一个惊喜,至少对我来说)。
  • Derived1:4个字节
  • ReallyDerived1:12个字节(每基类4个字节,因为多重继承的)
  • ReallyDerived2:8个字节(如猜到)
  • ReallyDerived3:4个字节(仅一个基类与虚拟继承的路径,但这是非虚拟的)。

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

+1

在这种情况下,如果有任何指向'vtables'的指针,我会感到惊讶。它至少需要3个。除了没有任何虚拟功能,它不需要任何虚拟功能。 (并且没有办法将'A *'转换为'B *')。 – 2012-03-16 13:43:20

+0

我猜**(如果有人知道,请纠正我!)编译器创建一个指向基类的指针,即使没有虚方法也是如此,因为多继承(因为它需要downcasts)。应该只有两个指向基类vtable的指针,一个用于基类,没有任何用于** C **本身,因为没有虚拟方法。
我的意思是:声明一个指针B * pB和另一个指针C * pC。然后向下转发指向D的指针并将其分配给pB和pC。 :) – 2012-03-16 13:52:26

+0

我正在使用g ++编译器 – Luv 2012-03-16 14:07:08

1

第一:没有虚函数,很可能在类中根本没有 vptr。您看到的8个字节是虚拟继承实施方式的工件 。

层次结构中的几个类通常可能共享相同的 vptr。发生这种情况时, 最终等级中的偏移量必须相同,并且 基类中的vtable条目列表必须是 派生类中vtable条目的列表的初始顺序。

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

在多重继承的情况下,总会有至少一个 类这些要求得不到满足,因为这两个基地 类不能有一个共同的起始地址,并且除非他们有确切 相同的虚拟功能,只有一个的虚拟表可能是另一个的初始序列 。

虚拟继承增加了另一个怪癖,因为 虚拟基地相对于从其继承的类的位置将根据层次结构的其余部分而变化 。我见过的大多数实现 都使用了一个单独的指针,尽管应该可以将 这个信息放在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

+0

检查超过2个基类,我认为这不是因为虚拟方法,而是因为指向基类“vtable”(即使不存在也存在)的指针 – 2012-03-16 14:03:28

+0

如果没有虚函数存在,“vtable”不是必要的,但是某些指向虚拟基类的指针(因为需要将'Derived *'转换为'VB *')。 – 2012-03-16 14:18:57

0

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

第一件事是您的示例中没有虚函数,这意味着没有任何类型实际上包含指向虚拟表的指针。那么这两个指针从哪里来? (我假设你在32位架构上)。那么,虚拟继承就是答案。当虚拟继承时,虚拟基(A)相对于派生类型(B,C)中的额外元素的相对位置将沿着继承链变化。对于B或C对象,编译器可以将类型设置为[A,B']和[A,C'](其中X'是X中不存在的额外字段)。如果虚拟继承意味着在D的情况下只有一个A子对象,那么编译器可以将D类型布置为[A,B',C',D]或[A,C',B ',D](或任何其他组合,A可能在对象的末尾,等等,这是在ABI中定义的)。那么这意味着什么呢?这意味着B和C的成员函数不能假设A子对象可能在哪里(在非虚拟继承的情况下,相对位置是已知的),因为类型可能实际上是一些其他类型的链条。

的解决问题的办法是,B和C通常含有一个额外的指针到基地指针,类似的,但不等同于虚拟指针。与vptr用于动态分派给函数的方式相同,这个额外的指针用于动态查找基础。

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