連結:
- C++虛函數探索筆記(1)——虛函數的簡單樣本分析
- C++虛函數探索筆記(2)——虛函數與多繼承
- C++虛函數探索筆記(3)——延伸思考:虛函數應用的一些其他情形
關注問題:
- 虛函數的作用
- 虛函數的實現原理
- 虛函數表在物件版面配置裡的位置
- 虛函數的類的sizeof
- 純虛函數的作用
- 多級繼承時的虛函數表內容
- 虛函數如何執行父類代碼
- 多繼承時的虛函數表定位,以及物件版面配置
- 虛解構函式的作用
- 虛函數在QT中的應用
- 虛函數與inline修飾符,static修飾符
虛解構函式
大家都知道,在C++裡需要自己嚴格管理好資源的分配和回收。通常情況下,在一個對象被析構的時候,是要由其釋放其申請到的各種資源的。最常見的,當然就是記憶體資源啦。
當只有一個類的時候,我們可以不用考慮太多,只要在解構函式裡檢查並釋放所有申請到的資源即可。但是在這個類繼承了一個抽象介面基類時,就有點點不一樣了。讓我們看看類的析構過程:
在大多數的類的使用時,通常都是直接刪除該類的執行個體對象,然後該類的解構函式就會被調用,從而使得這個類在解構函式裡執行的資源釋放代碼被執行到。
如果這個類繼承了其他類,那麼編譯器還會在這個類的解構函式裡自動添加對父類的解構函式的調用,從而將父類裡申請的資源也進行釋放。如果偶多個父類,也會依次調用各個解構函式。
倘若繼承的是一個抽象介面類,並且在程式運行期,可能通過一個基類指標將此對象釋放掉,那麼致命而又隱藏的記憶體泄露BUG就出現啦。。。因為試圖刪除的是基底物件,刪除時調用的是基類的解構函式,而基類的解構函式當然是不會去調用子類的解構函式的羅!
讓我們看看下面的代碼,使用vs2008編譯並啟動並執行時候,將會在程式運行結束時報告記憶體流失情況(如果要在linux下編譯測試,需要去掉第一行的include,以及return前的_CrtDumpMemoryLeaks()函數,然後使用linux下檢查記憶體泄露的工具進行測試)。
//Source filename: Win32Con.cpp#includeclass parent{public:parent(){}/*virtual */ ~parent(){}};class child:public parent{public:child(){p=new char[1000];}~child(){delete[] p;}char *p;};void free_child(parent *pp){delete pp;}int main(){child *obj=new child();free_child(obj);_CrtDumpMemoryLeaks();return 0;}
在這段代碼裡我們建立的是一個child類型的對象,然後使用free_child(parent*)函數來試圖釋放這個對象,這個時候,只會調用到parent::~parent()這個解構函式,而不會調用到child::~child()!
如何解決這個問題呢?
很簡單的,只要在parent::~parent()前增加 virtual關鍵字,將其變成一個虛函數。這樣,無論是以這個對象的父類指標進行刪除的時候,就會從虛函數表裡定位到子類child的解構函式,這樣就能夠從子類開始一級一級的向上調用解構函式,從而正確的將這個對象在各個繼承層次上申請的所有資源都釋放掉。
正因為這個原因,在很多C++編程原則的文章或者書裡都會提到這樣的原則:
如果一個類要被設計為可被繼承的基類,那麼其解構函式應該被聲明為虛函數。
虛函數在QT中的應用
在QT裡虛函數的應用非常的廣泛,事實上,在大多數的C++類庫裡都不可避免的要使用到虛函數。這裡簡單的列舉QT裡使用虛函數的情況:
QT的事件機制
是使用了虛函數的,你因此才可以自訂事件處理函數。比如最核心的QObject類的定義裡(在qobject.h裡),我們可以看到如下的虛函數定義:
virtual bool event(QEvent *);
virtual bool eventFilter(QObject *, QEvent *);
然後,在QWidget類繼承QObject類後重新實現了上面的兩個虛函數,完成很多視窗控制項類的預設事件處理。
當你要編寫自訂的QT控制項的時候,對event虛函數的重新實現就更是重要啦。
QT的訊號和槽
QT的槽函數可以被聲明為虛函數,所以雖然QT在實現訊號和槽機制的時候可能出於效率或者運行代價的原因未採用虛函數機制,但是我們依然可以在必要的時候使用虛函數來完成一些特定功能。比如為一些自訂控制項類抽象出來一個抽象介面基類,在做訊號和槽的串連的時候是對基類指標進行操作,而在基類裡的槽定義為虛函數,那麼虛函數在此依然可以實現訊號與槽的多態。
然而虛函數在調用的時候,一定要經曆查表的步驟,是存在一定的運行開銷的,對於一些非常頻繁的槽調用還是應該考慮到使用虛函數產生的代價的。
其他
在虛函數上,static和inline這兩個關鍵詞與virtual顯得很不友好。
從語義上即可看出,static和virtual完全就是衝突的,所以如果你試圖為一個虛函數增加一個static限定詞,那麼你的C++編譯器就會很負責任的報告一個嚴重錯誤給你。
而inline的含義和虛函數其實也是非常衝突的,但是inline在文法上只是給編譯器一個建議,而不是強制的語義限定,所以C++編譯器應該會忽略掉inline關鍵詞,繼續正常的編譯。
~END~