这与之前的许多问题类似,但是它提出了一些我无法找到答案的问题。
#include <iostream>
using namespace std;
class Base1 {
public:
int b1_data;
virtual void b1_fn() {cout << "I am b1\n";}
};
class Base2 {
public:
int b2_data;
virtual void b2_fn() {cout << "I am b2\n";}
};
class Derived : public Base1, public Base2 {
public:
int d_data;
void b1_fn() {cout << "I am b1 of d\n";}
void b2_fn() {cout << "I am b2 of d\n";}
};
int main() {
Derived *d = new Derived();
Base1 *b1 = d;
/*My observation mentioned below is implementation dependant, for learning,
I assume, there is vtable for each class containing virtual function and in
case of multiple inheritance, there are multiple vtables based on number of
base classes(hence that many vptr in derived object mem layout)*/
b1->b1_fn(); // invokes b1_fn of Derived because d points to
// start of d's memory layout and hence finds vtpr to
// Derived vtable for Base1(this is understood)
Base2 *b2 = d;
b2->b2_fn(); // invokes b2_fn of Derived but how? I know that it "somehow"
// gets the offset added to d to point to corresponding Base2
// type layout(which has vptr pointing to Derived vtable for
// Base2) present in d's memory layout.
return 0;
}
具体来说,b2如何指向vptr for Derived for Base2来获取b2_fn()?我曾经尝试过从gcc看到memlayout转储,但无法弄清楚。
答案 0 :(得分:2)
在多重继承的情况下,编译器构造他的vtable,以便每个子对象都有一个合适的vtable。当然这是依赖于实现的(作为vtable本身),但它的组织方式如下:
Base1
对象的vptr指向包含指向Base1::b1_fn
的唯一指针的vtable Base2
对象的vptr指向包含指向Base2::b2_fn
的唯一指针的vtable Derived
对象的vptr指向vtable,该vtable以与Base1
对应的vtable 布局 开头,但是将其扩展为缺少Base2
vtable的元素。使用&#34;布局&#34;我的意思是b1_fn()
的指针处于相同的偏移量,但它可能指向一个重写函数。因此,此处的表格将包含Derived::b1_fn
,后跟Derived::b2_fn
。这种组合布局可确保Base1
中的Derived
子对象可以与其子项共享vtable。 Derived
对象由2个子对象组成:所以Base1
子对象后面跟着一个Base2
子对象,它将有自己的vtable,使用所需的布局Base2
,但是再次使用Base2::b2_fn
而不是原始版本。 当将Derived
指针强制转换为Base2
指针时,编译器将通过应用在编译时确定的固定偏移量使其指向具有vtable的Base2
子对象。
顺便说一句,如果你要进行向下转换,编译器同样会在另一个方向上使用固定偏移量,以找到Derived
的开头。在使用虚拟基础之前,这一切都非常简单,其中固定偏移的技术不再有效。然后必须按照此other SO answer
这个Dr.Dobb's article是所有这些布局的最佳解释,带有一些好的图片。