Ayr*_*lin 5 c++ virtual inheritance
我对此很感兴趣virtual inheritance,这件事给我带来了神秘感。让我们考虑一个例子virtual inheritance:
struct Base {
virtual void v() { std::cout << "v"; }
};
struct IntermediateDerivedFirst : virtual Base {
virtual void w() { std::cout << "w"; }
};
struct IntermediateDerivedSecond : virtual Base {
virtual void x() { std::cout << "x"; }
};
struct Derived : IntermediateDerivedFirst, IntermediateDerivedSecond {
virtual void y() { std::cout << "y"; }
};
Run Code Online (Sandbox Code Playgroud)
最后,Derived应该看起来像这样:
--------
|[vtable]| -----> [ vbase offset 20 ]
|[vtable]|--- [ top offset 0 ]
|[vtable]|- | [ Derived typeinfo ]
-------- || [ IntermediateDerivedFirst::w() ]
|| [ Derived::y() ]
||
|----> [ vbase offset 12 ]
| [ top offset -8 ]
| [ Derived typeinfo ]
| [ IntermediateDerivedSecond::x()]
|
-----> [ vbase offset 0 ]
[ top offset -20 ]
[ Derived typeinfo ]
[ Base::v() ]
Run Code Online (Sandbox Code Playgroud)
因此,从字面上看,virtual继承移动vtable到最基类的末尾,并且正如我们所见 - s vtablefor不包含 for方法的地址。好吧,那么我们可以看到这个类有很少的s。让我们考虑一个代码:IntermediateDerivedFirstIntermediateDerivedSecondBasev()vtable
IntermediateDerivedFirst* fb = new Derived;
fb->v();
delete fb;
Run Code Online (Sandbox Code Playgroud)
然而,这个调用仍然有效,vtable因为IntermediateDerivedFirst没有有关v()方法的信息,而且它似乎在这里使用了一些魔法,并且它使用第三个vtable指针来调用v()。那么,编译器如何选择所需的vtable指针来获取正在调用的函数的地址呢?
小智 3
Bjarne Stroustroup 写了一篇关于使用 C++ 解决多重继承中的“钻石问题”的详细论文: Stroustrup,B Fall 1989,“C++ 的多重继承”。计算系统,卷。2 第 4 期,第 4 页。367-395
在两个 IntermediateDerived 类中声明“虚拟基”;保证您只获得公共基类的一个实例。
对于指定为 virtual 的每个不同基类,最派生的对象仅包含该类型的一个基类子对象,即使该类在继承层次结构中出现多次(只要每次都是继承 virtual 的)。
也就是说,编译器将为每个提供一个实例:
这两个 IntermediateDerived_X 类在其 vtable 中都会有一个虚拟指针,用于存储到 Base 类的偏移量。当任何一个 IntermediateDerived_X 类尝试访问 Base::v() 时,它都会使用其 vtable 中的虚拟指针来查找 Base 对象。沿着同一条线;如果 Derived 继承了 100 个以上具有相同“虚拟 Base”的 IntermediateDerived_X 类,则仍然只有一个 Base 实例。对 Base::v() 函数的调用使用虚拟指针来访问 Base 类的实例。
需要注意的是,Base 对象仅被构造一次;当它在派生类中首次初始化时。继承类将从基类到最派生类构建。反过来; 在示例实例化中,销毁顺序将从最派生到基础;调用“new Derived”将构造四个类中每个类的单个实例。
基于上面的地址表:IntermediateDerivedFirst 知道“vbase offset = 20”,这是 Base 对象 vtable 的位置。IntermediateDerivedSecond 可以以相同的方式访问 Base 类(vbase offset = 12);并会产生完全相同的功能。 没有神奇的第三个 vtable,而只是一个指向所有派生类之间共享的 Base vtable 的指针。
从实施的角度来看;通常,在所有虚拟类中添加指向基类的单个指针比将虚拟表复制到每个派生类更节省内存。不同的编译器、优化器、链接器等可能会以稍微不同的方式实现或更改生成的 vtable。但所有使用虚拟继承的派生类只会指向一个基对象。