我正在阅读关于如何选择主要基地的here:
" ... 2。如果C是动态类类型:
一个。标识直接或间接的所有虚拟基类,它们是某些其他直接或间接基类的主要基类。称这些间接主要基类。
湾如果C具有动态基类,则尝试选择主基类B.它是第一个(以直接基类顺序排列)非虚拟动态基类(如果存在)。否则,它是一个几乎为空的虚拟基类,是(预订)继承图顺序中的第一个,如果存在则不是间接主基类,或者如果它们都是间接原色,则只是第一个...&#34 ;
在出现这种纠正之后:
"上面的案例(2b)现在被认为是设计中的错误。使用第一个间接主基类作为派生类' primary base不会保存对象中的任何空间,并且会在基类虚拟表的附加副本中导致一些虚函数指针的重复。
好处是使用派生类虚拟指针作为基类虚拟指针通常会节省负载,并且调用其虚函数时不需要调整此指针。
有人认为2b会允许编译器在某些情况下避免调整它,但这是不正确的,因为虚函数调用算法要求通过指向定义函数的类的指针来查找函数,而不是一个只是继承它的人。 删除该要求不是一个好主意,因为那时不再能够通过它们跳转到的函数发出所有thunk。例如,请考虑以下示例:
struct A {virtual void f(); };
struct B:虚拟公共A {int i; };
struct C:virtual public A {int j; };
struct D:public B,public C {};
当声明B和C时,A在每种情况下都是主要基础,因此尽管在A-in-B和A-in-C vtable中分配了vcall偏移,但不需要进行此调整且不生成thunk 。但是,在D对象中,A不再是C的主要基础,所以如果我们允许调用C :: f()在C子对象中使用A的vtable副本,我们需要调整它从C *到B :: A *,这需要第三方thunk 。由于我们要求对C :: f()的调用首先转换为A *,所以从不引用C-in-D的A副本的副本,因此这不是必需的。"
请你用一个例子来解释一下这是什么意思:" 删除那个要求不是一个好主意,因为那时候不再有一种方法可以用它们跳转的函数发出所有的thunk至"
此外,什么是第三方thunk ?
我不明白引用的例子试图显示的内容。
答案 0 :(得分:0)
A
是一个几乎为空的类,只包含一个vptr且没有可见的数据成员:
struct A { virtual void f(); };
A
的布局是:
A_vtable *vptr
B
有一个几乎空的基类用作"主要":
struct B : virtual public A { int i; };
这意味着B
的布局从A
的布局开始,因此指向B
的指针是指向A
的指针(在程序集中)语言)。 B
子对象的布局:
B_vtable *A_vptr
int i
A_vptr
显然会指向B
vtable,它与A
vtable二进制兼容。
B_vtable
扩展了A_vtable
,添加了导航到虚拟基类A
的所有必要信息。
B
完整对象的布局:
A base_subobject
int i
同样适用于C
:
C_vtable *A_vptr
int j
C
完整对象的布局:
A base_subobject
int j
在D
中,显然只有一个A
子对象,因此完整对象的布局为:
A base_subobject
int i
not(A) not(base_subobject) aka (C::A)_vptr
int j
not(A)
表示A
几乎为空的基类,即A
的vptr,但不是真正的A
子对象:它看起来像一个A
但可见的A
上面有两个字。它是一个幽灵A
!
(C::A)_vptr
是vtable的vptr,其中C
的布局vtable(也是A
的布局vtable),但C
子对象{{1}最终不是主要基础:A
子对象失去了托管C
基类的权限。很明显,通过A
虚拟调用虚拟函数(C::A)_vptr
(只有一个:A
)需要A::f()
ajustement,thunk" {{ 1}}"接收指向this
的指针,并将其调整为C::A::f()
类型的真实not(base_subobject)
,即(示例中的上述两个字)。 (或者如果base_subobject
中有一个覆盖者,那么A
对象位于完全相同的地址,示例中会有两个字。)
所以给出了这些定义:
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
vptr完成的,也是A
vptr因为C
是一个&#34;静态&#34; C::A
的主要基础,然后代码应该有效,因为已经生成了从A
到C
的thunk。
在实践中,它似乎不适用于GCC,但如果您在C
中添加了一个覆盖:
D
它起作用,因为这样的具体功能在vtable C
的vtable中。
即使只使用纯虚拟覆盖:
struct C : virtual public A {
int j;
virtual void f()
{
std::cout << "C:f() \n";
}
};
和C::A
中的具体覆盖,它也有效:纯虚拟覆盖足以在struct C : virtual public A {
int j;
virtual void f() = 0;
};
的vtable中有适当的条目。