C++虛函數(shù)探索筆記(3)——延伸思考:虛函數(shù)應(yīng)用的一些其他情形
關(guān)注問題:
虛函數(shù)的作用
虛函數(shù)的實現(xiàn)原理
虛函數(shù)表在對象布局里的位置
虛函數(shù)的類的sizeof
純虛函數(shù)的作用
多級繼承時的虛函數(shù)表內(nèi)容
虛函數(shù)如何執(zhí)行父類代碼
多繼承時的虛函數(shù)表定位,以及對象布局
虛析構(gòu)函數(shù)的作用
虛函數(shù)在QT中的應(yīng)用
虛函數(shù)與inline修飾符,static修飾符
虛析構(gòu)函數(shù)
大家都知道,在C++里需要自己嚴(yán)格管理好資源的分配和回收。通常情況下,
在一個對象被析構(gòu)的時候,是要由其釋放其申請到的各種資源的。最常見的,當(dāng)
然就是內(nèi)存資源啦。
當(dāng)只有一個類的時候,我們可以不用考慮太多,只要在析構(gòu)函數(shù)里檢查并釋
放所有申請到的資源即可。但是在這個類繼承了一個抽象接口基類時,就有點點
不一樣了。讓我們看看類的析構(gòu)過程:
在大多數(shù)的類的使用時,通常都是直接刪除該類的實例對象,然后該類的析
構(gòu)函數(shù)就會被調(diào)用,從而使得這個類在析構(gòu)函數(shù)里執(zhí)行的資源釋放代碼被執(zhí)行到
。
如果這個類繼承了其他類,那么編譯器還會在這個類的析構(gòu)函數(shù)里自動添加
對父類的析構(gòu)函數(shù)的調(diào)用,從而將父類里申請的資源也進(jìn)行釋放。如果偶多個父
類,也會依次調(diào)用各個析構(gòu)函數(shù)。
倘若繼承的是一個抽象接口類,并且在程序運行期,可能通過一個基類指針
將此對象釋放掉,那么致命而又隱藏的內(nèi)存泄露BUG就出現(xiàn)啦……因為試圖刪除的
是基對象,刪除時調(diào)用的是基類的析構(gòu)函數(shù),而基類的析構(gòu)函數(shù)當(dāng)然是不會去調(diào)
用子類的析構(gòu)函數(shù)的羅!
讓我們看看下面的代碼,使用vs2008編譯并運行的時候,將會在程序運行結(jié)
束時報告內(nèi)存泄漏情況(如果要在linux下編譯測試,需要去掉第一行的include
,以及return前的_CrtDumpMemoryLeaks()函數(shù),然后使用linux下檢查內(nèi)存泄
露的工具進(jìn)行測試)。
//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;
}
在這段代碼里我們創(chuàng)建的是一個child類型的對象,然后使用free_child
(parent*)函數(shù)來試圖釋放這個對象,這個時候,只會調(diào)用到parent::
~parent()這個析構(gòu)函數(shù),而不會調(diào)用到child::~child()!
如何解決這個問題呢?
很簡單的,只要在parent::~parent()前增加 virtual關(guān)鍵字,將其變成
一個虛函數(shù)。這樣,無論是以這個對象的父類指針進(jìn)行刪除的時候,就會從虛函
數(shù)表里定位到子類child的析構(gòu)函數(shù),這樣就能夠從子類開始一級一級的向上調(diào)用
析構(gòu)函數(shù),從而正確的將這個對象在各個繼承層次上申請的所有資源都釋放掉。
正因為這個原因,在很多C++編程原則的文章或者書里都會提到這樣的原則:
如果一個類要被設(shè)計為可被繼承的基類,那么其析構(gòu)函數(shù)應(yīng)該被聲明為虛函
數(shù)。
虛函數(shù)在QT中的應(yīng)用
在QT里虛函數(shù)的應(yīng)用非常的廣泛,事實上,在大多數(shù)的C++類庫里都不可避免
的要使用到虛函數(shù)。這里簡單的列舉QT里使用虛函數(shù)的情況:
QT的事件機(jī)制
是使用了虛函數(shù)的,你因此才可以自定義事件處理函數(shù)。比如最核心的
QObject類的定義里(在qobject.h里),我們可以看到如下的虛函數(shù)定義:
virtual bool event(QEvent *);
virtual bool eventFilter(QObject *, QEvent *);
然后,在QWidget類繼承QObject類后重新實現(xiàn)了上面的兩個虛函數(shù),完成很
多窗口控件類的缺省事件處理。
當(dāng)你要編寫自定義的QT控件的時候,對event虛函數(shù)的重新實現(xiàn)就更是重要啦
。
QT的信號和槽
QT的槽函數(shù)可以被聲明為虛函數(shù),所以雖然QT在實現(xiàn)信號和槽機(jī)制的時候可
能出于效率或者運行代價的原因未采用虛函數(shù)機(jī)制,但是我們依然可以在必要的
時候使用虛函數(shù)來完成一些特定功能。比如為一些自定義控件類抽象出來一個抽
象接口基類,在做信號和槽的連接的時候是對基類指針進(jìn)行操作,而在基類里的
槽定義為虛函數(shù),那么虛函數(shù)在此依然可以實現(xiàn)信號與槽的多態(tài)。
然而虛函數(shù)在調(diào)用的時候,一定要經(jīng)歷查表的步驟,是存在一定的運行開銷
的,對于一些非常頻繁的槽調(diào)用還是應(yīng)該考慮到使用虛函數(shù)產(chǎn)生的代價的。
其他
在虛函數(shù)上,static和inline這兩個關(guān)鍵詞與virtual顯得很不友好。
從語義上即可看出,static和virtual完全就是沖突的,所以如果你試圖為一
個虛函數(shù)增加一個static限定詞,那么你的C++編譯器就會很負(fù)責(zé)任的報告一個嚴(yán)
重錯誤給你。
而inline的含義和虛函數(shù)其實也是非常沖突的,但是inline在語法上只是給
編譯器一個建議,而不是強(qiáng)制的語義限定,所以C++編譯器應(yīng)該會忽略掉inline關(guān)
鍵詞,繼續(xù)正常的編譯。