2014-05-19 57 views
2

我决定了解vtable是如何构建的,所以我打开调试器,发现了一些奇怪的东西。节点ptr包含几个vptr。我一直认为每个对象只有一个vptr。有人能向我解释这里发生了什么事吗? (我的意思是,当基类指针指向派生类对象)虚拟表格指针

#include <iostream> 
using namespace std; 

class Base 
{ 
    int base; 
public: 
    virtual void say() 
    { 
     cout << "Hello" << endl; 
    } 
    virtual void no() 
    { 
     cout << "No" << endl; 
    } 
}; 


class Base2 
{ 
public: 
    virtual void lol() 
    { 
     cout << "lol" << endl; 
    } 
}; 

class Derv:public Base,public Base2 
{ 
public: 
    void say() 
    { 
     cout << "yep" << endl; 
    } 

}; 


int main() 
{ 

    Base* ptr = new Derv(); 
    ptr->say(); 
    ptr = new Base(); 
    ptr->say(); 
} 

http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg

+0

我不明白你的意思是“节点ptr包含几个vptr”。你能准确地展示你看到你想要解释的是什么吗? –

+0

“Base”的另一个vtable和“Base2”的另一个vtable。编译器可以选择任何方法来实现虚拟表。我不认为vtables的结构在所有平台上都可以预期相同。 –

+0

[链接](http://s018.radikal.ru/i504/1405/1e/38832e978dd5.jpg) – GamovCoder

回答

4

需要两个指针,因为您有两个具有虚函数的基类。

让我们通过它一步一步:

首先定义Base其中有虚函数。因此,编译器会创建一个虚拟表,它大致如下所示(括号中给出的索引;注意,这是一个例子,确切的表布局将取决于编译器):

[0] address of Base::say() 
[1] address of Base::no() 

Base布局有将成为指向该表的字段__vptr(或者,如果它被命名,则会被命名)。当给定Base*类型的指针pBase并要求调用say时,编译器实际上会调用(p->__vptr[0])()

接下来,定义第二,独立的阶级Base2,其虚拟表看起来就像这样:

[0] address of Base2::lol() 

通过Base2指针lol呼叫现在将转换为类似(pBase2->__vptr[0])()

现在最后你定义了一个继承自BaseBase2的类Derv。这尤其意味着您可以同时指定Base*Base2*指向Derv类型的对象。现在,如果您只有一个__vptr,pBase->say()pBase2->lol()将调用相同的功能,因为它们都转换为(pXXX->__vptr[0])()

但是实际发生的事情是,有 __vptr领域,一为Base基类,以及一个用于_Base2基类。 Base*指向Base子对象,其__vptr,Base2*指向带有自己的__vptrBase2子对象。现在Derv虚拟表可以看起来像像这样:

[0] address of Derv::say() 
[1] address of Base::no() 
[2] address of Base2::lol() 

在该表的开始Base子对象点的__vptr,而Base2子对象点的__vptr到元素[2]。现在呼叫pBase->say()将转换为(pBase->__vptr[0])(),并且由于Base子对象的__vptr指向Derv的虚拟表的开始,所以它将最终根据预期呼叫Derv::say()。在另一方面,如果你调用pBase2->lol()将被翻译成(pBase2->__vptr[0])(),但由于pBase2指向Base2子对象OD Derv,它会因此取消引用相应__vptr指向Derv的虚表的元素[2]的地址在哪里存储Base2::lol。所以现在Base2::lol()被调用。

+0

感谢您的明确答案:) 总结: 因此,Derv没有自己的vtable,Derv使用两个指针而不是一个使用其他两个vtable? – GamovCoder

+0

@GamovCoder:'Derv'有一个vtable,但它没有单独的vptr。它使用它的每个基类的vptr(s)。 'Derv'的'Base'和'Base2'是彼此分开的,所以它们每个都有一个独立的vptr。 –

+0

我的意思是Derv的vtable包含两个独立的tables.It不是完整的。是吗? – GamovCoder

1

想想当你施放指针派生的指针基础发生了什么,就必须指与基本类型具有相同布局的内存块。当你有多重继承时,你将在每个具有虚函数的基础中结束一个vptr。

+0

我明白了,但我无法真正连接投射以及为什么包含Derived类'对象的地址的节点ptr包含3个vptr。我知道我们可以将一个Derived类的对象转换为一个Base类对象,但vptr的地址应该保持不变。我认为,当我们创建Derived类的对象时,它只有一个指针,该地址用适当的地址(派生类的vtable的地址)替换了地址 – GamovCoder

+0

@GamovCoder:我认为这更像是一个工件调试器如何呈现信息。根据调试器,如果仔细观察,有两个“Base”对象的副本,显然并非如此。 –