1

我在这里阅读有关如何选择主要碱基的信息:

"...2。如果 C 是动态类类型:

一个。识别所有直接或间接的虚拟基类,它们是某些其他直接或间接基类的主要基类。调用这些间接主要基类。

湾。如果 C 有一个动态基类,则尝试选择一个主基类 B。它是第一个(按直接基类顺序)非虚拟动态基类(如果存在)。否则,它是一个几乎为空的虚拟基类,是(预排序)继承图顺序中的第一个,如果存在的话,它不是间接主基类,或者如果它们都是间接主基类,则只是第一个......”

在进行此更正之后:

“上面的情况(2b)现在被认为是设计上的错误。使用第一个间接主基类作为派生类的主基并没有节省对象的任何空间,并且会造成一些虚函数的重复基类虚拟表的附加副本中的指针。

好处是使用派生类的虚指针作为基类的虚指针通常会节省负载,并且调用它的虚函数不需要对this指针进行调整。

人们认为 2b 将允许编译器在某些情况下避免调整它,但这是不正确的,因为虚函数调用算法要求通过指向定义函数的类的指针来查找函数,而不是仅仅继承它。删除该要求并不是一个好主意,因为这样就不再有办法用它们跳转到的函数来发出所有 thunk。例如,考虑这个例子:

结构 A { 虚拟 void f(); };

结构 B : 虚拟公共 A { int i; };

结构 C : 虚拟公共 A { int j; };

结构 D:公共 B,公共 C {};

当 B 和 C 被声明时,A 是每种情况下的主要基础,因此尽管在 A-in-B 和 A-in-C vtables 中分配了 vcall 偏移量,但不需要进行此调整,也不会生成 thunk。但是,在 D 对象内部,A 不再是 C 的主要基础,因此如果我们允许对 C::f() 的调用使用 C 子对象中 A 的 vtable 副本,我们需要将其从 C* 调整为B::A*,这需要第三方 thunk。因为我们要求对 C::f() 的调用首先转换为 A*,所以 C-in-D 的 A 的 vtable 副本永远不会被引用,所以这不是必需的。”

您能否举个例子解释一下这指的是什么:“删除该要求不是一个好主意,因为这样就不再有一种方法可以使用它们跳转到的函数发出所有 thunk ”?

另外,什么是第三方 thunk

我也不明白引用的示例试图显示什么。

4

1 回答 1

1

A是一个几乎是空的类,它只包含一个 vptr 而没有可见的数据成员:

struct A { virtual void f(); };

的布局A是:

A_vtable *vptr

B有一个几乎为空的基类用作“主要”:

struct B : virtual public A { int i; };

这意味着 的布局以 aB的布局开始A,因此指向 aB的指针是指向 a 的指针A(在汇编语言中)。B子对象的布局:

B_vtable *A_vptr
int i

A_vptr显然会指向一个Bvtable,它与Avtable 二进制兼容。

B_vtable扩展,添加所有必要的A_vtable信息来导航到虚拟基类A

B完整对象的布局:

A base_subobject
int i

同样适用于C

C_vtable *A_vptr
int j

C完整对象的布局:

A base_subobject
int j

inD显然只有一个A子对象,所以一个完整对象的布局是:

A base_subobject
int i
not(A) not(base_subobject) aka (C::A)_vptr
int j

not(A)是一个A几乎为空的基类的表示,即 的 vptr A,但不是真正的A子对象:它看起来像 anA但可见A的是上面的两个词。是鬼A

(C::A)_vptr是 vptr 到具有布局 vtable for 的 vtable C(因此也具有布局 vtable for A),但对于最终不是主要基础的子对象:C子对象已失去托管基类的特权。所以很明显,通过定义的虚拟函数的虚拟调用(只有一个:)需要一个调整,用一个 thunk“ ”接收一个指针并将其调整为类型的实数,即(示例中上面的两个词) . (或者,如果在 中存在覆盖器,则该对象位于完全相同的地址,例如上面的两个单词。)ACA(C::A)_vptrAA::f()thisC::A::f()not(base_subobject)base_subobjectADD

所以给出这些定义:

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,对于从 继承的虚函数,A调用是通过也是Cvptr的 vptr 完成的,C::A因为A它是 的“静态”主要基础C,那么代码应该可以工作,因为已经生成了一个 thunk从CD

实际上,它似乎不适用于 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.

测试代码:http ://codepad.org/AzmN2Xeh

于 2018-06-05T04:35:08.747 回答