我读过这里关于如何选择主基的文章:
"...2.如果C是一个动态类类型: a.标识所有直接或间接的虚拟基类,它们都是其他一些直接或间接基类的主基类。调用这些间接的主基类。 如果C有一个动态基类,尝试选择一个主基类b。如果存在非虚拟动态基类,它就是第一个(按直接基类顺序排列)非虚动态基类。否则,它是一个几乎为空的虚拟基类,第一个继承图顺序(如果存在的话)不是间接的主基类,或者只是第一个类,如果它们都是间接的主基类.“
在这一修正之后:
“以上情况(2b)现在被认为是设计中的一个错误。使用第一个间接主基类作为派生类的主基没有在对象中节省任何空间,并且会在基类虚拟表的附加副本中造成一些虚拟函数指针的重复。 这样做的好处是,使用派生类虚拟指针作为基类虚拟指针通常会节省负载,对其虚拟函数的调用不需要对该指针进行调整。 有人认为2b将允许编译器避免在某些情况下调整这一点,但这是不正确的,因为虚拟函数调用算法要求通过指向定义函数的类的指针来查找函数,而不是仅继承函数的类。删除该要求将不是一个好主意,因为这样就不再有一种方法可以发出带它们跳转到的函数的所有块。例如,请考虑下面的示例: 结构A{虚空f();}; 结构B:虚拟公共A{ int i;}; 结构C:虚拟公共A{ int j;}; 结构D:公共B,公共C {}; 当B和C被声明时,A在每种情况下都是一个主基,因此尽管在A in -B和A in -C vtable中分配vcall偏移量,但不需要进行这种调整,也不生成thunk。但是,在D对象中,A不再是C的主基,因此如果我们允许调用C::f()来使用C子对象中A的vtable的副本,则需要将其从C*调整为B::A*,这将需要一个第三方。由于我们要求对C::f()的调用首先转换为A*,所以从不引用C in-D的A的vtable副本,因此这是不必要的。
请您举例说明这是指什么:“删除该要求将不是一个好主意,因为这样就不再有一种方法可以发出所有函数跳转到的所有数据块”?
另外,什么是第三方数据块?
我也不明白引用的例子试图显示什么。
发布于 2018-06-05 04:35:08
A是一个几乎为空的类,它只包含vptr而不包含可见的数据成员:
struct A { virtual void f(); };A的布局如下:
A_vtable *vptrB有一个几乎为空的基类,用作“主”:
struct B : virtual public A { int i; };这意味着B的布局从A的布局开始,因此指向B的指针就是指向A的指针(在汇编语言中)。B子对象的布局:
B_vtable *A_vptr
int iA_vptr显然会指向一个B vtable,它与A vtable兼容。
B_vtable扩展了A_vtable,添加了导航到虚拟基类A所需的所有信息。
B完整对象的布局:
A base_subobject
int iC也一样
C_vtable *A_vptr
int jC完整对象的布局:
A base_subobject
int j在D中,显然只有一个A子对象,因此完整对象的布局是:
A base_subobject
int i
not(A) not(base_subobject) aka (C::A)_vptr
int jnot(A)是几乎为空的A基类的表示,即A的vptr,但不是真正的A子对象:它看起来像A,但可见的A是上面的两个单词。是鬼A!
(C::A)_vptr是vtable的vptr和C的布局vtable (也是A的布局vtable ),但是对于C子对象,A最终不是主基:C子对象失去了托管A基类的特权。因此很明显,通过(C::A)_vptr对定义为A的虚拟函数(只有一个:A::f())的虚拟调用需要一个this调整,其中的"C::A::f()“接收到指向not(base_subobject)的指针,并将其调整为A类型的实际base_subobject,即(示例中的两个单词)。(或者如果D中有一个过载者,那么位于完全相同地址的D对象,在示例中有两个单词)。
因此,鉴于这些定义:
struct A { virtual void f(); };
struct B : virtual public A { int i; };
struct C : virtual public A { int j; };
struct D : public B, public C {};不存在的A主基的幽灵值的使用是否有效?
D d;
C *volatile cp = &d;
A *volatile ghost_ap = reinterpret_cast<A*> (cp);
ghost_ap->f(); // use the vptr of C::A: safe?(volatile用于避免编译器传播类型知识)
如果对于C的lvalue,对于从A继承的虚拟函数,调用是通过C vptr完成的,它也是C::A vptr,因为A是C的“静态”主基,那么代码应该可以工作,因为已经生成了从C到D的thunk。
在实践中,这似乎不适用于GCC,但如果您在C中添加了一个超车者
struct C : virtual public A {
int j;
virtual void f()
{
std::cout << "C:f() \n";
}
};它的工作是因为这种具体的功能是在vtable的vtable的C::A。
即使只有一个纯粹的虚拟骑士:
struct C : virtual public A {
int j;
virtual void f() = 0;
};并且在D中有一个具体的覆盖程序,它也能工作:纯虚拟覆盖就足以在C::A的vtable中有适当的条目。
测试代码:http://codepad.org/AzmN2Xeh
https://stackoverflow.com/questions/44433386
复制相似问题