多重继承期间的对象构造和虚拟指针

时间:2016-02-06 17:38:32

标签: c++ multiple-inheritance vtable memory-layout vptr

class Base1 { 
    virtual void fun1() { cout << "Base1::fun1()" << endl; } 
    virtual void func1() { cout << "Base1::func1()" << endl; } 
}; 
class Base2 { 
    virtual void fun1() { cout << "Base2::fun1()" << endl; } 
    virtual void func1() { cout << "Base2::func1()" << endl; } 
}; 

class Test:public Base1,public Base2
{
public:
    virtual void test(){cout<<"Test";}
};


typedef void(*Fun)(void); 

int main()
{
    Test objTest; 
    Fun pFun = NULL;

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+0); pFun(); 
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+1); pFun(); 

//The following isnt supposed to print Test::test() right?
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+0)+2); pFun(); 

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+0); pFun(); 
    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+1); pFun();


//Isnt the following supposed to print Test:test() because the order of   
construction   object is Base1 followed by construction of Base2 followed by 
construction of Test.

    pFun = (Fun)*((int*)*(int*)((int*)&objTest+1)+2); pFun(); 
}

testof对象的sizeof是8个字节。 因此,从这个例子可以看出,该对象由两个4字节_vptr组成。由于继承顺序为public Base1,public Base2,这意味着对象应按以下方式进行:

| _vptr to class Base1 vTable | -->this Base1 vtable should have 2 elements.
| _vptr to class Base2 vTable | -->this Base2 vtable should have 3 elements.

但是从代码片段看,对象看起来像是:

| _vptr to class Base1 vTable | -->this Base1 vtable should have 3 elements.
| _vptr to class Base2 vTable | -->this Base2 vtable should have 2 elements.

第一个vptr指向一个包含3个函数指针的数组(第1个点Base1::fun1(),第2个点指向Base1::func1(),第三个指向Test::test())。

派生对象由Base + Derived组成。这意味着第一个字节块是Base对象,剩下的是Derived。 如果是这样,那么在我们的objTest示例中,第二个_vptr应该指向三个函数指针(1st到Base2::fun1()Base2::func1()Test::test())。但我们看到第一个_vptr指向Test::test()的函数指针。

问题:

1。此行为编译器是否具体?

2。标准是否提及有关此行为的任何内容?或者我的理解是完全错误的?

2 个答案:

答案 0 :(得分:0)

如何构造vtable绝对是依赖于编译器的。当你有多个继承时会发生什么。特别是这两个vtable的生成顺序......

除了v​​table之外还存储了其他东西 - 例如类型信息,因此可以进行动态投射。

标准所要求的只是&#34;虚拟功能工作&#34; (当然,它有成千上万的文字来描述&#34;工作&#34;意味着什么),但它的实现方式完全取决于编译器[和某种程度上的C ++库]。

答案 1 :(得分:0)

首先,请注意vtable超出了C ++标准的范围,因为标准只涉及符合程序的行为,而不是实现如何实现。

所有已知的实现都使用vtable来实现虚函数和RTTI,但是在更多“微妙”特性中存在重要的细微差别,如多重继承,协变返回和虚拟基础。

在更简单的情况下,可以扩展基类的vtable,因为基类子对象与派生对象位于同一地址;基类被称为“主要基础”。

在更复杂的情况下,派生类具有多个多态基类,每个类都有自己的vptr。一个基类将是主要基类,派生类将扩展其vtable。对象的布局是这样的,其他基类子对象将在派生对象中处于非零偏移,因此当通过这种非主要的vtable调用虚函数时,this指针将需要调整基础;该vtable中引用的函数需要一个指向基类子对象或完整对象的指针:

  • 基类的vtable中引用的函数需要指向完整基础对象的指针;
  • 派生对象的非主基础vtable中引用的函数需要指向基础子对象的指针;为了获得正确的this指针,他们需要为派生调整做一个基础。

由于需要进行调整,通过非主要基地调用虚拟功能的效率略低。编译器更喜欢使用主vptr。

编译器将使用从其他基类继承的所有虚函数扩展主基的vtable。