0
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(); 
}

测试对象的大小为 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 个函数指针组成的数组(第一个指向Base1::fun1(),第二个指向Base1::func1(),第三个指向Test::test())。

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

问题:

1.这种行为编译器是特定的吗?

2.标准是否提到了这种行为?还是我的理解完全错误?

4

2 回答 2

0

首先,请注意 vtables 超出了 C++ 标准的范围,因为该标准只关注符合标准的程序的行为,而不是实现如何实现这一点。

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

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

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

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

由于需要调整,通过非主要基础调用虚函数的效率略低。编译器会更喜欢使用主 vptr。

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

于 2016-03-10T03:08:44.463 回答
0

vtable 的构造方式绝对取决于编译器。当你有多重继承时会发生什么。特别是两个 vtable 的生成顺序......

除了 vtable 之外,还存储了其他东西 - 例如类型信息,因此可以进行动态转换。

标准所要求的只是“虚拟函数工作”(当然,描述“工作”的含义有数千个词),但它的实现方式完全取决于编译器[在某种程度上可能是 C++ 库]。

于 2016-02-06T17:53:08.000 回答