2014-05-19 55 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」對象的副本,顯然並非如此。 –