2015-05-03 34 views
-1

如何識別vptr是否將用於調用虛函數?編譯器如何決定何時使用vPtr調用函數

考慮下面的層次結構:

class A 
{ 
    int n; 
public: 
    virtual void funcA() 
    {std::cout <<"A::funcA()" << std::endl;} 
}; 

class B: public A 
{ 
    public: 
    virtual void funcB() 
    {std::cout <<"B::funcB()" << std::endl;} 
}; 

A* obj = new B(); 

obj->funcB(); //1. this does not even compile 

typedef void (*fB)(); 
fB* func; 
int* vptr = (int*)obj; //2. Accessing the vptr 

func = (fB*)(*vptr); 
func[1](); //3. Calling funcB using vptr. 

聲明1.即obj-> funcB();甚至沒有編譯,儘管Vtable有一個funcB的條目,在那裏訪問vPtr間接funcB()可以被成功調用。

編譯器如何決定何時使用vTable來調用函數? 在聲明中A * obj = new B();因爲我使用的是基類指針,所以我認爲應該使用vtable來調用函數。

以下是間接訪問vptr時的內存佈局。 Memory layout

+1

下面的(2)行表現出未定義的行爲。 「似乎工作」是未定義行爲的一種可能表現形式。 –

+1

當然,它不會編譯。沒有'A :: funcB()'。 – Barry

+2

'編譯器如何決定何時使用vTable來調用一個函數?它決定這麼做是爲了滿足C++語言的指定語義。爲什麼你關心這些實現細節?你想解決什麼問題? –

回答

0

添加到Barry的評論,添加行virtual void funcB() = 0;class A似乎解決了這個問題。

+0

不回答問題。 –

2

因此有兩個回答你的問題:

  1. 簡短的一個是:
    obj->FuncB()僅僅是一個合法的呼叫,如果靜態obj(在這種情況下A)具有功能FuncB與適當的簽名(直接或由於基類)。只有在這種情況下,編譯器纔會根據FuncB是否聲明爲虛擬或不在聲明A(或其基本類型)中決定它是否將其轉換爲直接或動態函數調用(例如使用vtable) 。

  2. 較長的一個是這樣的:
    當編譯器看到obj->funcB()它沒有辦法知道(優化除外),方式是什麼obj的運行時類型,尤其是它不知道,是否派生類中實現根本不存在funcB()obj可能例如在另一個翻譯單元中創建,或者它可能是一個函數參數。

    不,那個信息通常不存儲在虛擬功能表:
    V表僅僅是地址和沒有先驗知識某個特定addess對應於稱爲funcB,編譯器可以函數數組不會用它來實現obj->funcB()號召 - 或者更確切地說:標準不允許這樣做。先前的知識只能由靜態類型obj(或其基類)中的虛擬函數聲明提供。

    原因是,爲什麼在調試器中可以使用該信息(它的行爲在標準之外),因爲它可以訪問調試符號,而調試符號通常不是分佈式發行版二進制文件的一部分。默認情況下,將信息存儲在vtable中會浪費內存和性能,因爲程序不允許以您描述的方式在標準C++中使用它。對於像C++/CLI這樣的擴展可能是一個不同的故事。

+0

謝謝邁克非常好的解釋 – anonymous

+0

@anonymous:您的歡迎 - 如果您認爲它完全回答您的問題,隨時接受它。 – MikeMB

+0

@MikeMB關於可能的優化,或多或少你可以說,如果你正在調用方法的對象已知(某種程度上通過優化)與它的指針類型相同(即沒有發生向上轉換)函數調用可以靜態解析,否則,它可能已被上傳,所以我們最好查閱vtable(以多態方式解析)? –

相關問題