2012-06-05 66 views
1
#include <iostream> 
using namespace std; 

class Base { 
public: 
    Base() { 
     cout << "In Base" << endl; 
     cout << "Virtual Pointer = " << (int*)this << endl; 
     cout << "Address of Vtable = " 
     << (int*)*(int*)this << endl; 
     cout << "Value at Vtable = " 
     << (int*)*(int*)*(int*)this << endl; 
     cout << endl; 
    } 

    virtual void f1() { cout << "Base::f1" << endl; } 
}; 

class Drive : public Base { 
public: 
    Drive() { 
     cout << "In Drive" << endl; 
     cout << "Virtual Pointer = " 
     << (int*)this << endl; 
     cout << "Address of Vtable = " 
     << (int*)*(int*)this << endl; 
     cout << "Value at Vtable = " 
     << (int*)*(int*)*(int*)this << endl; 
     cout << endl; 
    } 

    virtual void f1() { cout << "Drive::f2" << endl; } 
}; 

int main() { 
Drive d; 
return 0; 

}爲什麼啓動驅動器實例

這個程序的輸出時基類和驅動器類具有相同的虛擬指針,但2虛函數表是

In Base 
Virtual Pointer = 0012FF7C 
Address of Vtable = 0046C08C 
Value at Vtable = 004010F0 

In Drive 
Virtual Pointer = 0012FF7C 
Address of Vtable = 0046C07C 
Value at Vtable = 00401217 

按照代碼,我可以看到,當我創建一個Drive對象時,Base構造函數也運行並顯示與Drive的虛擬指針相同的虛擬指針的地址:0012FF7C。奇怪的是,當我在Base和Drive類的構造函數中取消引用該地址時,它指向不同的值,這意味着有兩個vtable,一個在0046C08C,另一個在0046C07C。在Drive對象的結構中很難理解,並且在1個指針指向2地址時也很難理解。

回答

3

您剛剛目睹了您的C++編譯器如何實現C++標準規定的規則。雖然Base構造函數正在運行,但對虛擬方法的任何調用都需要分派給Base實現,即使Drive會覆蓋它們。爲了做到這一點,你的C++編譯器顯然會使對象的vtable指針指向Basevtable。當Base構造函數完成並且在Drive構造函數中繼續執行時,vtable指針將更新爲指向Drivevtable。

這最終成爲實現它的一種便捷方式。編譯器生成指令調用虛擬方法的其餘代碼不需要更改以檢測是否需要特殊的構造函數行爲。它可以像往常一樣在vtable中查找。更改vtable指針是在構造函數運行時更改對象的有效運行時類型的快速方法。

您可能會發現析構函數以類似的方式工作,但相反。如果構造一個Base對象而不是Drive對象,則可能會看到與您的Drive對象的構造函數Base中的類似地址。

+0

非常感謝你 –

+1

請注意,這種特殊的行爲可能會被優化。一個好的優化器可以看到在你的例子中沒有使用最初的'Base'vtable。初始的vtable需要從'Base :: Base'間接調用'Base :: f1',但是你沒有這樣做。 – MSalters

5

隨着對象構造的發生,它會在步驟中發生。第一步是初始化基類,在該過程中對象的基礎對象 - 該vtable將反映基類的方法。當構造進行到派生類時,vtable被更新爲派生類的vtable。

這一切都是由C++標準制定的,當然除了標準沒有強制實施vtables。

+1

非常感謝你。我愛你 –

+3

@cao_bang,以及我認爲「愛」有點強烈,但無論如何你都歡迎。 –