我试图理解书中有效的c ++语句。以下是多继承的继承图。
现在这本书说vptr需要每个类中单独的内存。它还做了以下声明
上图中的一个奇怪之处是即使涉及四个类,也只有三个vptrs。 实现可以自由生成四个vptrs,如果他们喜欢的话,但三个就足够了(事实证明B和D可以共享一个vptr),并且大多数实现都利用这个机会来减少编译器生成的开销。
我看不出为什么每个类都需要为vptr分配内存的原因。我理解vptr是从基类继承的,可能是继承类型。如果我们假设它显示了带有继承的vptr的结果内存结构,那么它们如何使语句
B和D可以共享vptr
有人可以在多重继承中澄清一下vptr吗?
答案 0 :(得分:22)
答案 1 :(得分:1)
如果某个班级有虚拟成员,则需要找到他们的地址。它们被收集在一个常量表(vtbl)中,其地址存储在每个对象(vptr)的隐藏字段中。对虚拟成员的呼叫基本上是:
obj->_vptr[member_idx](obj, params...);
将虚拟成员添加到其基类的派生类也需要一个位置。因此新的vtbl和新的vptr为他们。对继承的虚拟成员的调用仍然是
obj->_vptr[member_idx](obj, params...);
并且对新虚拟成员的调用是:
obj->_vptr2[member_idx](obj, params...);
如果基础不是虚拟的,则可以安排第二个vtbl紧接在第一个之后,有效地增加了vtbl的大小。并且不再需要_vptr2。因此,对新虚拟成员的调用是:
obj->_vptr[member_idx+num_inherited_members](obj, params...);
在(非虚拟)多重继承的情况下,一个继承两个vtbl和两个vptr。它们不能合并,并且调用必须注意向对象添加偏移量(以便在正确的位置找到继承的数据成员)。对第一个基类成员的调用将是
obj->_vptr_base1[member_idx](obj, params...);
和第二次
obj->_vptr_base2[member_idx](obj+offset, params...);
新的虚拟成员可以再次放入新的vtbl中,也可以附加到第一个基地的vtbl中(这样在将来的调用中不会添加任何偏移量。)
如果一个基础是虚拟的,那么就不能将新的vtbl附加到继承的基础上,因为它可能导致冲突(在你给出的例子中,如果B和C都附加了它们的虚函数,那么D是如何构建它的版本?)。
因此,A需要一个vtbl。 B和C需要一个vtbl,它不能附加到A的一个,因为A是两者的虚拟基础。 D需要一个vtbl但它可以附加到B一,因为B不是D的虚拟基类。
答案 2 :(得分:0)
这一切都与编译器如何计算方法函数的实际地址有关。编译器假定虚拟表指针位于距对象基础的已知偏移处(通常在偏移0处)。编译器还需要知道每个类的虚拟表的结构 - 换句话说,如何查找虚拟表中函数的指针。
B类和C类将具有完全不同的虚拟表结构,因为它们具有不同的方法。 D类的虚拟表看起来像是B类的虚拟表,后面跟着C类方法的附加数据。
当您生成D类对象时,可以将其转换为指向B的指针或指向C的指针,甚至可以作为指向A类的指针。您可以将这些指针传递给甚至不知道存在的模块D类,但可以调用类B或C或A的方法。这些模块需要知道如何定位指向类的虚拟表的指针,他们需要知道如何定位指向类B / C /的方法的指针A在虚拟表中。这就是为什么你需要为每个类都有单独的VPTR。
D类很清楚B类的存在及其虚拟表的结构,因此可以扩展其结构并重用来自对象B的VPTR。
当您将指向对象D的指针转换为指向对象B或C或A的指针时,它实际上会通过某个偏移更新指针,以便它从对应于该特定基类的vptr开始。
答案 3 :(得分:0)
我看不出有什么理由 需要单独的记忆 每个类为vptr
在运行时,当您通过指针调用(虚拟)方法时,CPU不知道调度方法的实际对象。如果您有B* b = ...; b->some_method();
,那么变量b可能会指向通过new B()
或new D()
创建的对象或
偶数new E()
其中E
是继承自({1}}或B
的其他类。这些类中的每一个都可以为D
提供自己的实现(覆盖)。因此,调用some_method()
应根据b指向的对象从b->some_method()
,B
或D
调度实现。
对象的vptr允许CPU查找对该对象有效的some_method实现的地址。每个类定义它自己的vtbl(包含所有虚方法的地址),并且该类的每个对象都以指向该vtbl的vptr开始。
答案 4 :(得分:0)
我认为D需要2或3个vptrs。
这里A可能需要也可能不需要vptr。 B需要一个不应与A共享的(因为A实际上是遗传的)。 C需要一个不应该与A共享的(同上)。 D可以使用B或C的vftable作为其新的虚函数(如果有的话),因此它可以共享B或C。
我的旧论文“C ++:Under the Hood”解释了虚拟基类的Microsoft C ++实现。 http://www.openrce.org/articles/files/jangrayhood.pdf
和(MS C ++)您可以使用cl / d1reportAllClassLayout进行编译,以获取类内存布局的文本报告。
快乐的黑客攻击!