我正在尋找一些關於虛擬表格的信息,但找不到任何易於理解的東西。
有人可以給我很好的例子(不是來自Wiki,請解釋或鏈接?)爲什麼我們需要虛擬桌面?
回答
沒有虛擬表格,你將無法使運行時多態性工作,因爲所有對函數的引用都將在編譯時被綁定。一個簡單的例子
struct Base {
virtual void f() { }
};
struct Derived : public Base {
virtual void f() { }
};
void callF(Base *o) {
o->f();
}
int main() {
Derived d;
callF(&d);
}
功能callF
裏面,你只知道o
指向一個Base
對象。但是,在運行時,代碼應該調用Derived::f
(因爲Base::f
是虛擬的)。在編譯時,編譯器無法知道o->f()
調用將執行哪個代碼,因爲它不知道o
指向哪個代碼。
因此,你需要一個叫做「虛擬表」的東西,它基本上是一個函數指針表。每個具有虛函數的對象都有一個「虛表指針」,它指向虛表的類型對象。
在callF
函數上面的代碼然後只需要查找爲Base::f
在虛擬表中的條目(它發現基於所述v表指針中的對象),並且然後它調用函數表條目指着。那可能是Base::f
但它也可能指向其他 - Derived::f
,例如。
這意味着由於虛擬表,您可以在運行時擁有多態性,因爲被調用的實際函數是在運行時通過查找虛擬表中的函數指針,然後通過該指針調用函數來確定的 - 而不是直接調用函數(就像非虛函數一樣)。
簡短回答:虛函數調用basePointer-> f()意味着不同的事情取決於basePointer的歷史。如果它指向真的是派生類的東西,則會調用一個不同的函數。
爲此,編譯器做了一個簡單的函數指針遊戲。要爲不同類型調用的函數地址存儲在虛擬表中。
虛擬表不僅用於函數指針。 RTTI機制將其用於運行時類型信息(獲取由某個基本類型的地址引用的對象的實際類型)。
一些新的/刪除實現會將對象大小存儲在虛擬表中。
Windows COM編程使用虛擬表來破解它並將其作爲接口推送。
要回答您的標題問題 - 您沒有,並且C++標準沒有指定您必須提供一個。你想要的是能夠說:
struct A {
virtual ~A() {}
virtual void f() {}
};
struct B : public A {
void f() {}
};
A * p = new B;
p->f();
並且B :: f被調用而不是A :: f。虛擬函數表是實現這一點的一種方式,但是對於普通的C++程序員來說,坦率地說並不感興趣 - 我只是在回答這樣的問題時想到它。
有關替代方法的示例,python將方法和屬性直接存儲在對象中。它因此完成了這種行爲,但不使用「虛擬」表格,儘管它非常相似。 – 2010-06-09 09:46:28
@Matthieu M,python與C++完全相同 - 它存儲對方法對象的一些引用(在用指針實現的c-python中),而C++存儲方法的地址。區別主要在於python表按每個對象而不是按類存儲,因爲python允許在運行時添加屬性和方法。 – gnud 2010-08-24 09:09:59
我真的不同意:在C++中,虛擬表沒有指向函數存儲的指針的實際信息,編譯器只知道所需的方法在給定的索引處。另一方面,在python中,屬性和方法(通常)被存儲在一個字典中,你可以按名稱查找。這是實現中的一個主要區別,它使python以性能爲代價獲得更大的靈活性。 – 2010-08-24 16:29:32
假設Player
和Monster
繼承自定義虛擬name()
操作的抽象基類Actor
。進一步假設你有要求的演員,他的名字的函數:
void print_information(const Actor& actor)
{
std::cout << "the actor is called " << actor.name() << std::endl;
}
這是不可能在編譯時推斷演員是否會真正成爲一名球員或一個怪物。由於它們有不同的方法,決定調用哪個方法必須推遲到運行時。編譯器爲每個角色對象添加附加信息,以便在運行時作出此決定。
在每一個編譯器,我知道,這些額外的信息是一個指針(通常稱爲的vptr)函數指針(通常稱爲VTBL)特定於具體類的表。也就是說,所有玩家對象共享相同的虛擬表,其中包含指向所有玩家方法的指針(對於怪物也是如此)。在運行時,通過從應該在其上調用方法的對象的vptr所指向的vtbl中選擇方法來找到正確的方法。
虛函數表是一個實現細節 - 這是編譯器如何在類中實現多態方法。
考慮
class Animal
{
virtual void talk()=0;
}
class Dog : Animal
{
virtual void talk() {
cout << "Woof!";
}
}
class Cat : Animal
{
virtual void talk() {
cout << "Meow!";
}
}
現在我們必須
A* animal = loadFromFile("somefile.txt"); // from somewhere
animal->talk();
我們怎麼知道這些talk()
版本被稱爲?動物對象有一張表格,指向與該動物一起使用的虛擬功能。例如,talk
可能是在第三偏移,如果有兩個其他的虛擬方法:
dog
[function ptr for some method 1]
[function ptr for some method 2]
[function ptr for talk -> Dog::Talk]
cat
[function ptr for some method 1]
[function ptr for some method 2]
[function ptr for talk -> Cat::Talk]
當我們有一個Animnal
例如,我們不知道哪個talk()
方法調用。我們通過查看虛擬表並獲取第三個條目來找到它,因爲編譯器知道對應於talk
指針(編譯器知道Animal上的虛擬方法,因此知道vtable中指針的順序。)
給定一個Animal,爲了調用正確的talk()方法,編譯器添加代碼來獲取第三個函數指針並使用它。然後,這指向適當的實現。
對於非虛擬方法,這不是必需的,因爲被調用的實際函數可以在編譯時確定 - 只有一個可能的函數可以被調用用於非虛擬調用。
- 1. 爲什麼我們需要虛擬內存?
- 2. 爲什麼我們需要抽象類而不是虛擬類?
- 3. Android爲什麼需要虛擬機(DVM)?
- 4. 爲什麼我想模擬的屬性需要虛擬?
- 5. 虛擬機與虛擬桌面有什麼不同?
- 6. 爲什麼我們有邏輯內存時需要虛擬內存?
- 7. 爲什麼我需要虛擬專用服務器(VPS)?
- 8. 爲什麼我需要C#EF中的ID和虛擬集合?
- 9. 爲什麼我們需要使用虛擬〜A()= default;而不是在C++ 11中虛擬〜A(){}?
- 10. 什麼需要更多的虛擬機?
- 11. 爲什麼我們需要創建模擬對象?
- 12. DavLockDB:我們需要什麼?
- 13. 爲什麼我們需要simple_one_for_one?
- 14. 爲什麼我們需要TensorFlow tf.Graph?
- 15. 爲什麼我們需要使用prompt.start()?
- 16. 爲什麼我們需要scalaz.stream迭代?
- 17. 爲什麼我們需要WCF
- 18. 爲什麼我們需要Anaconda 2.7?
- 19. 爲什麼我們需要編寫log.isDebugEnabled?
- 20. 爲什麼我們需要ng-click?
- 21. 爲什麼我們需要Control.Lens.Reified?
- 22. 我們爲什麼需要纖維
- 23. 爲什麼我們需要org.junit.ComparisonFailure?
- 24. 爲什麼我們需要addRequestHeader方法?
- 25. 爲什麼我們需要時代?
- 26. 爲什麼我們需要ContinueWith方法?
- 27. 爲什麼我們需要Java NIO Selector?
- 28. 爲什麼我們需要使用ExitWindowsEx
- 29. 爲什麼我們需要包裝類
- 30. 爲什麼我們需要在JavaScript
*你*不需要它,但編譯器。 – EJP 2017-02-13 23:53:05