C++ Virtual總結

虛函數表
C++中的虛函數的實現一般是通過虛函數表(C++規範並沒有規定具體用哪種方法,但大部分的編譯器廠商都選擇此方法)。
類的虛函數表是一塊連續的內存,每個內存單元中記錄一個JMP指令的地址。
注意的是,編譯器會爲每個有虛函數的類創建一個虛函數表,該虛函數表將被該類的所有對象共享。類的每個虛成員佔據虛函數表中的一行。如果類中有N個虛函數,那麼其虛函數表將有N*4字節的大小。
虛函數(Virtual Function)是通過一張虛函數表來實現的。簡稱爲V-Table。在這個表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
編譯器應該是保證虛函數表的指針存在於對象實例中最前面的位置(這是爲了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)。 這意味着可以通過對象實例的地址得到這張虛函數表,然後就可以遍歷其中函數指針,並調用相應的函數。

===================================================================================================================

c++是一門面向對象的語言,但是它和c#,java不同,它沒有反射機制。沒有反射機制使得c++在語言的一些設計方面與其他語言有點不一樣,主要體現在智能化方面,許多東西得程序員明確指定,例如本文要講的virtual關鍵字。virtual是在運行時才體現的,而c++在運行時無法使用反射來確定當前類的父類是否有此方法,此方法是否被重載等信息,所以必須在寫代碼時用virtual來明確指明,然後通過編譯器做一些特殊處理(也就是使用虛表)。

我們見到virtual最多的地方是在c++裏面的多態實現。

  1. // dynamic_poly.h  
  2. #include <iostream>  
  3. // 公共抽象基類Vehicle  
  4. class Vehicle  
  5. {  
  6. public:  
  7.     virtual void run() const = 0;  
  8. };  
  9. // 派生於Vehicle的具體類Car  
  10. class Car: public Vehicle  
  11. {  
  12. public:  
  13.     virtual void run() const  
  14.     {  
  15.         std::cout << "run a car/n";  
  16.     }  
  17. };  
  18. // 派生於Vehicle的具體類Airplane  
  19. class Airplane: public Vehicle  
  20. {  
  21. public:  
  22.     virtual void run() const  
  23.     {  
  24.         std::cout << "run a airplane/n";  
  25.     }  
  26. };   
 

  1. // dynamic_poly_1.cpp  
  2. #include <iostream>  
  3. #include <vector>  
  4. #include "dynamic_poly.h"  
  5. // 通過指針run任何vehicle  
  6. void run_vehicle(const Vehicle* vehicle)  
  7. {  
  8.     vehicle->run();            // 根據vehicle的具體類型調用對應的run()  
  9. }  
  10. int main()  
  11. {  
  12.     Car car;  
  13.     Airplane airplane;  
  14.     run_vehicle(&car);         // 調用Car::run()  
  15.     run_vehicle(&airplane);    // 調用Airplane::run()  
  16. }  
 

上面的例子來自於http://www.vckbase.com/document/viewdoc/?id=948,這篇文章裏面還提到了多態的一些別的例子。關於多態的文章還有孟巖寫的一篇http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855906.aspx,可以看看。

在很多多態的例子中,我們都可以看到將基類的方法聲明爲純虛函數(virtual void run() const = 0;),這樣可以要求子類必須實現這個方法,同時可以體現面向接口編程。

對於使用了virtual之後編譯器會做些什麼,我覺得用代碼更容易說明問題。下面的例子來自http://www.cppblog.com/zhangyq/archive/2009/06/13/87597.html

1 編譯器會爲這個類的虛函數添加一個虛表,類似下面的: 
// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp 
// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Base::__vtable[5] = { 
   &Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4 
};

2 然後增加一個指向虛表的指針爲每一個類對象,這個指針是隱藏的 
// Your original C++ source code 
class Base { 
public: 
   ... 
   FunctionPtr* __vptr;  ← supplied by the compiler, hidden from the programmer 
   ... 
}; 
3 編譯器在構造中初始化這個指針 
Base::Base(...arbitrary params...) 
   : __vptr(&Base::__vtable[0])  ← supplied by the compiler, hidden from the programmer 
   ... 

   ... 

在派生類中,它也會增加一個隱藏的虛表,但是它可以overrides基類的虛函數如: 
// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp 
// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Der::__vtable[5] = { 
   &Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4 
};   

從上面的代碼我們可以非常容易的劃出class的結構圖,當然可以用/d1reportSingleClassLayout來顯示class的佈局圖(http://blog.csdn.net/chief1985/archive/2009/10/23/4720191.aspx)。

使用virtual的地方還有虛擬繼承和虛擬析構函數。

虛擬繼承的例子如下,來自http://blog.csdn.net/wuliming_sc/archive/2009/01/31/3855607.aspx

  1. class Point2d{  
  2. public:  
  3. //...  
  4. protected:  
  5. float _x;  
  6. float _y;  
  7. };  
  8. class Vertex : public virtual Point2d{  
  9. public:  
  10. //...  
  11. protected:  
  12. Vertex *next;  
  13. };  
  14. class Point3d : public virtual Point2d{  
  15. public:  
  16. //...  
  17. protected:  
  18. float _z;  
  19. };  
  20. class Vertex3d: public Vertex, public Point3d{  
  21. public:  
  22. //...  
  23. protected:  
  24. float mumble;  
  25. };  
 

image

使用虛擬繼承一般都是出現了菱形繼承(c++允許多重繼承),這種情況下若不使用虛擬繼承,便會導致以下情況:

1.公共基類子對象的重複創建問題。

2.成員函數的名字衝突問題

3. 數據成員的名字衝突問題

發佈了11 篇原創文章 · 獲贊 9 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章