2009-06-09 88 views
5

我有時候讀過(可能在c.l.C++。moderated)虛擬函數調用可以模板化。我嘗試了以下幾行。虛擬呼叫使用純虛擬成員的地址。它合法嗎?

#include <iostream> 

template<class T, class FUN> 
void callVirtual(T& t, FUN f){ 
    (*t.*f)(); 
} 


struct Base{ 
    virtual ~Base(){} 
    virtual void sayHi()=0; 
}; 


struct Derived : public Base{ 
    void sayHi(){ 
     std::cout << "Hi!" << std::endl; 
    } 
}; 


void Test(){ 
    Base* ptr = new Derived; 
    callVirtual(ptr,&Base::sayHi); 
} 

int main() 
{ 
    Test(); 
    return 0; 
} 

Output: 
Hi! 

模板化方法雖然在編譯時給出了純虛擬基本成員方法的地址,但在運行時調用了正確的方法。 在標準C++中使用純虛擬成員的地址是否合法?

在此先感謝

編輯-1:我刪除了問題的第二部分「它是如何工作的?」。看起來這就是引人注目的。

EDIT-2:我搜索了c.l.C++。moderated,碰到了這個linkhttp://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/5ddde8cf1ae59a0d)。共識似乎是因爲標準不限制它,它是vaild。

編輯-3:在閱讀codeproject文章(感謝ovanes)之後,我在想編譯器會有一些神奇的功能。由於虛函數是通過vtable(編譯器特定的)實現的,因此獲取虛函數的地址總是會在vtable中給出偏移量。根據所使用的'this'指針,調用相應的函數(其地址在偏移量處)。我不知道該如何去證明這一點,因爲標準沒有說出任何東西!

回答

0

我想這是未定義的。我在規範中找不到任何東西。

使用名爲vtable的概念實現虛擬方法。

我會說它是編譯器實現特定的。我真的不認爲它是一個純粹的虛擬,如果它只是虛擬的,也會發生。

我剛剛編譯你的代碼與Visual Studio 2008和拆解EXE。 VS2008所做的是創建一個thunk函數,使用傳入的'this'指針跳轉到vtable條目。

這是對callVirtual模板函數的設置和調用。

push offset [email protected]@$B3AE ; void (__thiscall *)(Base *) 
lea  eax, [ebp+ptr] 
push eax    ; Base ** 
call [email protected]@@[email protected]@@[email protected]@[email protected]@Z ; callVirtual<Base *,void (Base::*)(void)>(Base * &,void (Base::*)(void)) 

因此,傳遞函數指針thunk函數:[email protected]@$B3AE

; void __thiscall Base___vcall_(Base *) 
[email protected]@$B3AE proc near 
jmp  [email protected]@$B3AE ; [thunk]: Base::`vcall'{4,{flat}} 
[email protected]@$B3AE endp 

所有thunk函數是使用V表跳轉到真正的類方法做。

+0

感謝。你知道C++標準的一部分,它提到了指向純虛擬的成員函數指針。我試圖找到,但無法找到任何具體的細節。在我看來,由於允許指向不完整類型的成員指針,因此也允許指向純虛擬的成員指針。 – Abhay 2009-06-09 07:35:43

+0

我錯讀了這個問題。我會更新我的答案。 – 2009-06-09 21:43:44

+0

+1您編輯的答案接近這個問題的答案:-)。我開始認爲考慮虛擬函數(純粹與否)的地址並調用它更接近於實現定義的語言方面。 – Abhay 2009-06-10 06:01:58

1

當然可以。您的驗證碼已不僅僅是通話這樣的純虛方法不同:

void Test() 
{ 
    Base* ptr = new Derived; 
    ptr->sayHi(); 
    delete ptr; 
} 

唯一的區別是,你有另一種機制做呼叫,在這種情況下,通過callVirtual()。

1

正如Magnus Skog所說,模板部分並不真正相關。什麼它歸結爲是:

(ptr->* &Base::sayHi)() 

似乎工作,但

ptr->Base::sayHi() 

顯然不會因爲sayHi的是純虛函數。

我一直沒有找到什麼在標準關於當你採取虛擬,或純虛函數的地址時,會發生什麼。我不確定這是否合法。它可以在GCC和MSVC中工作,而Comeau的在線編譯器也不會抱怨。

編輯

即使是有效的,因爲你的編輯說,我仍然不知道這意味着什麼。

如果我們爲簡單起見假設sayHi是非純的(所以定義爲Base::sayHi存在),那麼如果我把它的地址發生了什麼呢?我是否得到Base :: sayHi的地址,或者vtable指向的函數的地址(在這種情況下是Derived :: sayHi)?

顯然,編譯器會假設後者,但爲什麼? 調用ptr->Base::sayHi()在基類中調用sayHi,但服用的Base::sayHi地址給我的Derived::sayHi

在我看來,不一致的地址。這是我背後的一些理由嗎?

1

以下文章廣泛討論了C++中的成員函數指針,它們如何實現以及缺陷在哪裏。它還處理虛擬成員函數指針等等。我認爲它會回答你所有的問題。

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

它也展示瞭如何實現代表在C++中,哪些陷阱你可能陷阱。

隨着親切的問候,

Ovanes