C++虛函數探索筆記(3)——延伸思考:虛函數應用的一些其他情形
關注問題:
虛函數的作用
虛函數的實現原理
虛函數表在對象布局里的位置
虛函數的類的sizeof
純虛函數的作用
多級繼承時的虛函數表內容
虛函數如何執行父類代碼
多繼承時的虛函數表定位,以及對象布局
虛析構函數的作用
虛函數在QT中的應用
虛函數與inline修飾符,static修飾符
虛析構函數
大家都知道,在C++里需要自己嚴格管理好資源的分配和回收。通常情況下,
在一個對象被析構的時候,是要由其釋放其申請到的各種資源的。最常見的,當
然就是內存資源啦。
當只有一個類的時候,我們可以不用考慮太多,只要在析構函數里檢查并釋
放所有申請到的資源即可。但是在這個類繼承了一個抽象接口基類時,就有點點
不一樣了。讓我們看看類的析構過程:
在大多數的類的使用時,通常都是直接刪除該類的實例對象,然后該類的析
構函數就會被調用,從而使得這個類在析構函數里執行的資源釋放代碼被執行到
。
如果這個類繼承了其他類,那么編譯器還會在這個類的析構函數里自動添加
對父類的析構函數的調用,從而將父類里申請的資源也進行釋放。如果偶多個父
類,也會依次調用各個析構函數。
倘若繼承的是一個抽象接口類,并且在程序運行期,可能通過一個基類指針
將此對象釋放掉,那么致命而又隱藏的內存泄露BUG就出現啦……因為試圖刪除的
是基對象,刪除時調用的是基類的析構函數,而基類的析構函數當然是不會去調
用子類的析構函數的羅!
讓我們看看下面的代碼,使用vs2008編譯并運行的時候,將會在程序運行結
束時報告內存泄漏情況(如果要在linux下編譯測試,需要去掉第一行的include
,以及return前的_CrtDumpMemoryLeaks()函數,然后使用linux下檢查內存泄
露的工具進行測試)。
//Source filename: Win32Con.cpp
#include
class 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關
鍵詞,繼續正常的編譯。