而不是使用虛擬函數查找對象中的vtable指針,然後將其帶到包含指向函數的指針的vtable中 - 是不可能在對象中包含數據成員直接指向函數?可能爲虛擬函數實現旁路虛擬表?
回答
如果我理解你的問題,你正在尋找一種使用函數指針來實現多態的方法。
嗯,這是可能的,但非常麻煩,容易出錯,但它將難以勝過由編譯器生成的虛擬函數調用。
怎麼辦?
這個想法是使用一個函數指針。爲了實現多態,它必須在基類中。
class B { // Base class
protected:
void (B::*fptest)(); // Function pointer to member function
public:
void mtest() // Class specific mebmer function
{ cout << "Base test\n"; }
void itest() // Polymorphic function
{ (this->*fptest)(); } // implemented by calling the poitner to member function
B() : fptest(&B::mtest) { } // Ctor must initialize the function pointer
virtual ~B() {}
};
class D : public B { // Derived class
public:
void mtest() // Class specific mebmer function
{ cout << "Derived test\n"; }
D() // Ctor
{ fptest = reinterpret_cast<void(B::*)()>(&D::mtest); } // not sure it's this safe in case of multiple inheritance !!
};
代碼來測試這個結構:
B b;
D d;
B *pb = &b, *pd = &d;
pb->itest();
pd->itest();
安全嗎?
這有嚴重的侷限性。例如:
- 您必須確保每個派生類都正確地初始化函數指針。
- 在多重繼承的情況下,轉換爲基類成員的函數指針可能無法按預期工作。
- 指針可能沒有超載。所以你需要爲每個可能的簽名指定一個不同的指針。這可能很奇怪。
它比vtable查找性能更高嗎?
編號:看,每多態調用執行itest()
彙編:
; 41 : pd->itest(); // cod for the call for a derived object
mov ecx, DWORD PTR _pd$[ebp] ; load the oject address
call [email protected]@@QAEXXZ ; call B::itest
; 16 : void itest() { (this->*fptest)(); }
push ebp
mov ebp, esp
sub esp, 68 ; 00000044H
push ebx
push esi
push edi
mov DWORD PTR _this$[ebp], ecx ; use address of object as parameter
mov eax, DWORD PTR _this$[ebp] ; load the function pointer
mov ecx, DWORD PTR _this$[ebp] ; " "
mov edx, DWORD PTR [eax+4] ; call the function pointer
call edx
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret 0
當然,優化器可以內聯的代碼,刪除一些push和pop,但總的原則是,對於碼將產生間接性。
是不是足夠的查找性能?
一個虛擬表查找基本上是從編譯器計算的固定偏移量加載函數指針。對於callling一個vitual測試()函數是這樣的彙編代碼:
39 : pd->test();
mov eax, DWORD PTR _pd$[ebp]
mov edx, DWORD PTR [eax]
mov ecx, DWORD PTR _pd$[ebp]
mov eax, DWORD PTR [edx]
call eax
結論
V表查找是至少爲高性能的,通過一個函數指針的調用。編譯器負責所有的初始化和最複雜的繼承情況。更好地使用虛擬功能的強大功能,而不是嘗試手動勝過編譯器。
沒有開啓優化,這個測試並沒有真正展示任何有用的東西。 – 2014-11-24 23:17:36
@MarkRansom最好的做法可能是爲OP的特定情況做一個基準測試,因爲你不能從過於簡化的演示代碼上進行優化來推廣。 – Christophe 2014-11-24 23:53:40
- 1. 實現虛擬路徑
- 2. 虛擬智能卡實現
- 3. 虛擬函數機制實現
- 4. 重載函數(虛擬/非虛擬)
- 5. 虛擬IMG SRC現實路徑
- 6. 虛基在虛擬函數表中的偏移爲虛擬繼承
- 7. 虛擬函數C++
- 8. 虛擬函數C#
- 9. 具有虛擬和非虛擬功能的unique_ptr :: get()函數
- 10. 實現虛擬鍵盤PC
- 11. fido-u2f虛擬實現
- 12. 問題,實現虛擬功能
- 13. 虛擬功能實現C++不工作
- 14. 沒有虛擬構造函數但是虛擬析構函數
- 15. 虛擬函數C++:虛擬函數已經有一個主體
- 16. 覆蓋虛擬功能到非虛擬功能可以嗎?
- 17. 虛擬功能
- 18. 虛擬功能
- 19. 使用/不使用虛擬方法實現純虛擬方法?
- 20. 虛擬化的實現也是虛擬的嗎?
- 21. 如何調用(非虛擬)虛擬方法的原始實現?
- 22. 虛擬路徑null?
- 23. 可能創建虛擬錨?
- 24. 虛擬函數可以被非虛函數覆蓋嗎?
- 25. WP7實現數據虛擬化
- 26. 爲什麼虛擬函數必須在超類中實現?
- 27. 爲什麼此方法在未標記爲虛擬時表現爲虛擬?
- 28. 虛擬表C++
- 29. 虛擬表
- 30. Rails 4虛擬記錄(不是虛擬屬性) - 可能嗎?
如果編譯器知道對象的靜態類型,它可以將該調用優化爲直接調用。我認爲這種優化是常見的,但我不是100%確定的。如果編譯器無法做到這一點,那麼在對象中使用自己的指針(以及需要利用它的任何代碼)可能會比僅在vtable中使用指針的編譯器優化得少得多。 – 2014-11-24 20:33:29
如果在每個對象中添加所有指針,那麼對象的大小是多少? – quantdev 2014-11-24 20:34:12
這意味着,您爲每個虛擬成員在每個實例中都有一個數據成員。這有點快但體積更大。您只能爲該呼叫節省一個間接尋址,但會浪費大量浪費的數據空間。 – Klaus 2014-11-24 20:34:40