虚拟的对象大小

我有一些关于虚拟对象大小的问题。

1)虚拟function

class A { public: int a; virtual void v(); } 

A类的大小是8字节….一个整数(4字节)加上一个虚拟指针(4字节)很明显!

 class B: public A{ public: int b; virtual void w(); } 

B级的大小是多less? 我testing使用sizeof B,它打印12

这是否意味着即使B类和A类都具有虚拟function,只有一个vptr存在? 为什么只有一个vptr?

 class A { public: int a; virtual void v(); }; class B { public: int b; virtual void w(); }; class C : public A, public B { public: int c; virtual void x(); }; 

C的大小是20 ……..

看来在这种情况下,两个vptrs在布局…..这是怎么发生的? 我认为这两个vptrs之一是为甲类,另一个是乙类….所以没有vptr为C类的虚函数?

我的问题是,有关inheritance的vptrs数量的规则是什么?

2)虚拟inheritance

  class A { public: int a; virtual void v(); }; class B: virtual public A{ //virtual inheritance public: int b; virtual void w(); }; class C : public A { //non-virtual inheritance public: int c; virtual void x(); }; class D: public B, public C { public: int d; virtual void y(); }; 

A的大小是8个字节————– 4(int a)+ 4(vptr)= 8

B的大小是16字节————–没有虚拟它应该是4 + 4 + 4 = 12。为什么这里还有4个字节? Bclass的布局是什么?

C的大小是12个字节。 ————– 4 + 4 + 4 = 12.很清楚!

D的大小是32字节————–它应该是16(B类)+12(C类)+4(int d)= 32。对吗?

  class A { public: int a; virtual void v(); }; class B: virtual public A{ //virtual inheritance here public: int b; virtual void w(); }; class C : virtual public A { //virtual inheritance here public: int c; virtual void x(); }; class D: public B, public C { public: int d; virtual void y(); }; 

A的大小是8

B的大小是16

C的大小是16

D的大小是28是否意味着28 = 16(B类)+16(C类)-8(A类)+4(这是什么?)

我的问题是,为什么虚拟inheritance应用时有额外的空间?

在这种情况下,对象尺寸下面的规则是什么?

虚拟应用于所有基类和部分基类时有什么区别?

这是所有的实现定义。 我正在使用VC10 Beta2。 帮助理解这个东西的关键(虚拟函数的实现),你需要知道Visual Studio编译器/ d1reportSingleClassLayoutXXX中的一个秘密开关。 我会在一秒钟之内。

基本规则是vtable需要位于偏移量为0的任何指向对象的指针。 这意味着多个vtables的多重inheritance。

几个问题在这里,我会从顶部开始:

这是否意味着即使B类和A类都具有虚拟function,只有一个vptr存在? 为什么只有一个vptr?

这就是虚拟函数的工作原理,您希望基类和派生类共享相同的vtable指针(指向派生类中的实现)。

看来在这种情况下,两个vptrs在布局…..这是怎么发生的? 我认为这两个vptrs之一是为甲类,另一个是乙类….所以没有vptr为C类的虚函数?

这是C类的布局,由/ d1reportSingleClassLayoutC报告:

 class C size(20): +--- | +--- (base class A) 0 | | {vfptr} 4 | | a | +--- | +--- (base class B) 8 | | {vfptr} 12 | | b | +--- 16 | c +--- 

你是对的,有两个vtable,每个基类一个。 这是多重inheritance的工作原理。 如果将C *转换为B *,则指针值将被调整8个字节。 为了使虚拟函数调用起作用,虚拟表仍然需要在偏移量0处。

上面的类A中的vtable被视为C类的vtable(当通过C *调用时)。

B的大小是16字节————–没有虚拟它应该是4 + 4 + 4 = 12。为什么这里还有4个字节? Bclass的布局是什么?

在这个例子中,这是B类的布局:

 class B size(20): +--- 0 | {vfptr} 4 | {vbptr} 8 | b +--- +--- (virtual base A) 12 | {vfptr} 16 | a +--- 

正如你所看到的,有一个额外的指针来处理虚拟inheritance。 虚拟inheritance是复杂的。

D的大小是32字节————–它应该是16(B类)+12(C类)+4(int d)= 32。对吗?

不,36字节。 与虚拟inheritance同样的处理。 在这个例子中D的布局:

 class D size(36): +--- | +--- (base class B) 0 | | {vfptr} 4 | | {vbptr} 8 | | b | +--- | +--- (base class C) | | +--- (base class A) 12 | | | {vfptr} 16 | | | a | | +--- 20 | | c | +--- 24 | d +--- +--- (virtual base A) 28 | {vfptr} 32 | a +--- 

我的问题是,为什么虚拟inheritance应用时有额外的空间?

虚拟基类指针,很复杂。 基类是虚拟inheritance中的“组合”。 而不是将基类embedded到类中,该类将具有指向布局中的基类对象的指针。 如果您有两个使用虚拟inheritance的基类(“菱形”类层次结构),则它们将指向对象中相同的虚拟基类,而不是具有该基类的单独副本。

在这种情况下,对象尺寸下面的规则是什么?

重要的一点; 没有规则:编译器可以做任何需要做的事情。

最后的细节 使我正在编译的所有这些类布局图:

 cl test.cpp /d1reportSingleClassLayoutXXX 

其中XXX是您想要查看布局的结构/类的子string匹配。 使用这个你可以自己探索各种inheritancescheme的影响,以及为什么/在哪里添加填充等等。

Quote>我的问题是,关于在inheritance的vptrs数量的规则是什么?

没有规则,每个编译器供应商都被允许以他认为合适的方式实现inheritance的语义。

B类:public A {},size = 12。这是非常正常的,一个虚拟表的B有两个虚拟方法,vtable指针+ 2 * int = 12

C类:public A,public B {},size = 20。C可以任意扩展A或B的vtable。2 * vtable指针+ 3 * int = 20

虚拟inheritance:这就是你真正触及无证行为的边缘。 例如,在MSVC中#pragma vtordisp和/ vd编译选项变得相关。 本文中有一些背景信息。 我研究了几次,并决定编译选项首字母缩略词代表我的代码可能发生什么,如果我曾经使用它。

思考这个问题的一个好方法就是理解处理上演所需要做的事情。 我将尝试通过显示所描述类的对象的内存布局来回答你的问题。

代码示例#2

内存布局如下:

  vptr |  A :: a | 乙:: B 

上传一个指向B的指针以inputA将导致相同的地址,使用相同的vptr。 这就是为什么在这里不需要额外的vptr。

代码示例#3

  vptr |  A :: a |  vptr |  B :: b |  Ç中:C 

正如你所看到的,这里有两个vptr,就像你猜的一样。 为什么? 因为如果我们从C上传到A,我们不需要修改地址,因此可以使用相同的vptr。 但是如果我们从C上传到B,我们确实需要这种修改,相应地,我们需要在结果对象的开始处有一个vptr。

因此,超越第一个的任何inheritance类将需要额外的vptr(除非那个inheritance的类没有虚拟方法,在这种情况下它没有vptr)。

代码示例#4及以后

当你实际上派生的时候,你需要一个叫做基指针的新指针来指向派生类的内存布局中的位置。 当然可以有多个基址指针。

那么内存布局怎么样呢? 这取决于编译器。 在你的编译器中可能是这样的

  vptr | 基指针|  B :: b |  vptr |  A :: a |  C :: c |  vptr |  A ::一
           \ ----------------------------------------- ^ 

但是其他编译器可能会在虚拟表中包含基本指针(通过使用偏移 – 值得另一个问题)。

你需要一个基指针,因为当你以虚拟的方式派生的时候,派生类只会在内存布局中出现一次(如果它也是正常导出的话,它可能会出现一些额外的时间),所以它的所有孩子都必须指向完全相同的位置。

编辑:澄清 – 这一切都取决于编译器,我显示的内存布局可以在不同的编译器不同。

所有这些都是您实现的完全实现定义。 你不能指望它。 没有“规则”。

在inheritance的例子中,这里是A和B类的虚拟表可能是这样的:

  class A +-----------------+ | pointer to A::v | +-----------------+ class B +-----------------+ | pointer to A::v | +-----------------+ | pointer to B::w | +-----------------+ 

正如你所看到的,如果你有一个指向B类虚拟表的指针,它也可以完全有效的作为A类的虚拟表。

在你的C类的例子中,如果你想一想,就没有办法做一个虚拟表,这个虚拟表对于C类,A类和B类都是有效的。所以编译器会做两个。 一个虚拟表适用于A类和C类(大部分可能),另一个对A类和B类有效。

这显然取决于编译器的实现。 无论如何,我认为我可以从下面链接的经典文章给出的实现中总结出以下规则,并给出了您在示例中获得的字节数(除了D类,它将是36字节而不是32 !!!) :

T类对象的大小是:

  • 它的字段的大小加上Tinheritance的每个对象的大小的总和加上T个虚拟inheritance的每个对象的PLUS 4字节PLUS 4字节只有如果T需要另一个v表
  • 注意:如果一个类K几乎被多次(在任何级别上)inheritance,你只需要添加一次K的大小

所以我们必须回答另一个问题:什么时候一个类需要另一个V表?

  • 只有具有一个或多个虚拟方法的类才能从其他类inheritance
  • 换句话说,一个类只需要另一个v表就可以了。如果没有它实际上inheritance的类没有一个v表

规则的结束(我认为这可以用来匹配特里马哈菲在他的回答中所解释的)

无论如何,我的build议是阅读Bjarne Stroustrup(C ++的创build者)的以下文章,它正是这样解释这些事情:虚拟或非虚拟inheritance需要多less虚拟表…以及为什么!

这真的是一个很好的阅读: http : //www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html

我不知道,但我认为这是因为指向虚拟方法表