詳解 QT 父子與 QT 對(duì)象delete
詳解 QT 父子與 QT 對(duì)象delete是本文要介紹的內(nèi)容,講到了父對(duì)象和子對(duì)象的關(guān)系,很詳細(xì)。不多說了,先來看內(nèi)容。
很多C/C++初學(xué)者常犯的一個(gè)錯(cuò)誤就是,使用malloc、new分配了一塊內(nèi)存卻忘記釋放,導(dǎo)致內(nèi)存泄漏。Qt的對(duì)象模型提供了一種Qt對(duì)象之間的父子關(guān)系,當(dāng)很多個(gè)對(duì)象都按一定次序建立起來這種父子關(guān)系的時(shí)候,就組織成了一顆樹。當(dāng)delete一個(gè)父對(duì)象的時(shí)候,Qt的對(duì)象模型機(jī)制保證了會(huì)自動(dòng)的把 它的所有子對(duì)象,以及孫對(duì)象,等等,全部delete,從而保證不會(huì)有內(nèi)存泄漏的情況發(fā)生。
任何事情都有正反兩面作用,這種機(jī)制看上去挺好,但是卻會(huì)對(duì)很多Qt的初學(xué)者造成困擾,我經(jīng)常給別人回答的問題是:1,new了一個(gè)Qt對(duì)象之后,在什么 情況下應(yīng)該delete它?2,Qt的析構(gòu)函數(shù)是不是有bug?3,為什么正常delete一個(gè)Qt對(duì)象卻會(huì)產(chǎn)生segment fault?等等諸如此類的問題,這篇文章就是針對(duì)這個(gè)問題的詳細(xì)解釋。
在每一個(gè)Qt對(duì)象中,都有一個(gè)鏈表,這個(gè)鏈表保存有它所有子對(duì)象的指針。當(dāng)創(chuàng)建一個(gè)新的Qt對(duì)象的時(shí)候,如果把另外一個(gè)Qt對(duì)象指定為這個(gè)對(duì)象的父對(duì)象, 那么父對(duì)象就會(huì)在它的子對(duì)象鏈表中加入這個(gè)子對(duì)象的指針。另外,對(duì)于任意一個(gè)Qt對(duì)象而言,在其生命周期的任何時(shí)候,都還可以通過setParent函數(shù) 重新設(shè)置它的父對(duì)象。當(dāng)一個(gè)父對(duì)象在被delete的時(shí)候,它會(huì)自動(dòng)的把它所有的子對(duì)象全部delete。當(dāng)一個(gè)子對(duì)象在delete的時(shí)候,會(huì)把它自己 從它的父對(duì)象的子對(duì)象鏈表中刪除。
QWidget是所有在屏幕上顯示出來的界面對(duì)象的基類,它擴(kuò)展了Qt對(duì)象的父子關(guān)系。一個(gè)Widget對(duì)象也就自然的成為其父Widget對(duì)象的子 Widget,并且顯示在它的父Widget的坐標(biāo)系統(tǒng)中。例如,一個(gè)對(duì)話框(dialog)上的按鈕(button)應(yīng)該是這個(gè)對(duì)話框的子 Widget。
關(guān)于Qt對(duì)象的new和delete,下面我們舉例說明。
例如,下面這一段代碼是正確的:
- int main()
- {
- QObject* objParent = new QObject(NULL);
- QObject* objChild = new QObject(objParent);
- QObject* objChild2 = new QObject(objParent);
- delete objParent;
- }
我們用一張圖來描述這三個(gè)對(duì)象之間的關(guān)系:
在上述代碼片段中,objParent是objChild的父對(duì)象,在objParent對(duì)象中有一個(gè)子對(duì)象鏈表,這個(gè)鏈表中保存它所有子對(duì)象的指針,在 這里,就是保存了objChild和objChild2的指針。在代碼的結(jié)束部分,就只有delete了一個(gè)對(duì)象objParent,在 objParent對(duì)象的析構(gòu)函數(shù)會(huì)遍歷它的子對(duì)象鏈表,并且把它所有的子對(duì)象(objChild和objChild2)一一刪除。所以上面這段代碼是安 全的,不會(huì)造成內(nèi)存泄漏。
如果我們把上面這段代碼改成這樣,也是正確的:
- int main()
- {
- QObject* objParent = new QObject(NULL);
- QObject* objChild = new QObject(objParent);
- QObject* objChild2 = new QObject(objParent);
- delete objChild;
- delete objParent;
- }
在這段代碼中,我們就只看一下和上一段代碼不一樣的地方,就是在delete objParent對(duì)象之前,先delete objChild對(duì)象。在delete objChild對(duì)象的時(shí)候,objChild對(duì)象會(huì)自動(dòng)的把自己從objParent對(duì)象的子對(duì)象鏈表中刪除,也就是說,在objChild對(duì)象被 delete完成之后,objParent對(duì)象就只有一個(gè)子對(duì)象(objChild2)了。然后在delete objParent對(duì)象的時(shí)候,會(huì)自動(dòng)把objChild2對(duì)象也delete。所以,這段代碼也是安全的。
Qt的這種設(shè)計(jì)對(duì)某些調(diào)試工具來說卻是不友好的,比如valgrind。比如上面這段代碼,valgrind工具在分析代碼的時(shí)候,就會(huì)認(rèn)為objChild2對(duì)象沒有被正確的delete,從而會(huì)報(bào)告說,這段代碼存在內(nèi)存泄漏。哈哈,我們知道,這個(gè)報(bào)告是不對(duì)的。
我們?cè)诳匆豢催@一段代碼:
- int main()
- {
- QWidget window;
- QPushButton quit("Exit", &window);
- }
在這段代碼中,我們創(chuàng)建了兩個(gè)widget對(duì)象,第一個(gè)是window,第二個(gè)是quit,他們都是Qt對(duì)象,因?yàn)镼PushButton是從 QWidget派生出來的,而QWidget是從QObject派生出來的。這兩個(gè)對(duì)象之間的關(guān)系是,window對(duì)象是quit對(duì)象的父對(duì)象,由于他們 都會(huì)被分配在棧(stack)上面,那么quit對(duì)象是不是會(huì)被析構(gòu)兩次呢?我們知道,在一個(gè)函數(shù)體內(nèi)部聲明的變量,在這個(gè)函數(shù)退出的時(shí)候就會(huì)被析構(gòu),那 么在這段代碼中,window和quit兩個(gè)對(duì)象在函數(shù)退出的時(shí)候析構(gòu)函數(shù)都會(huì)被調(diào)用。
那么,假設(shè),如果是window的析構(gòu)函數(shù)先被調(diào)用的話,它就會(huì)去 delete quit對(duì)象;然后quit的析構(gòu)函數(shù)再次被調(diào)用,程序就出錯(cuò)了。事實(shí)情況不是這樣的,C++標(biāo)準(zhǔn)規(guī)定,本地對(duì)象的析構(gòu)函數(shù)的調(diào)用順序與他們的構(gòu)造順序相 反。那么在這段代碼中,這就是quit對(duì)象的析構(gòu)函數(shù)一定會(huì)比window對(duì)象的析構(gòu)函數(shù)先被調(diào)用,所以,在window對(duì)象析構(gòu)的時(shí)候,quit對(duì)象已 經(jīng)不存在了,不會(huì)被析構(gòu)兩次。
如果我們把代碼改成這個(gè)樣子,就會(huì)出錯(cuò)了,對(duì)照前面的解釋,請(qǐng)你自己來分析一下吧。
- int main()
- {
- QPushButton quit("Exit");
- QWidget window;
- quit.setParent(&window);
- }
但是我們自己在寫程序的時(shí)候,也必須重點(diǎn)注意一項(xiàng),千萬不要delete子對(duì)象兩次,就像前面這段代碼那樣,程序肯定就crash了。
最后,讓我們來結(jié)合Qt source code,來看看這parent/child關(guān)系是如何實(shí)現(xiàn)的。
在本專欄文章的第一部分“對(duì)象數(shù)據(jù)存儲(chǔ)”,我們說到過,所有Qt對(duì)象的私有數(shù)據(jù)成員的基類是QObjectData類,這個(gè)類的定義如下:
- typedef QList<QObject*> QObjectList;
- class QObjectData
- {
- public:
- QObject *parent;
- QObjectList children;
- // 忽略其它成員定義
- };
我們可以看到,在這里定義了指向parent的指針,和保存子對(duì)象的列表。其實(shí),把一個(gè)對(duì)象設(shè)置成另一個(gè)對(duì)象的父對(duì)象,無非就是在操作這兩個(gè)數(shù)據(jù)。把子對(duì) 象中的這個(gè)parent變量設(shè)置為指向其父對(duì)象;而在父對(duì)象的children列表中加入子對(duì)象的指針。當(dāng)然,我這里說的非常簡單,在實(shí)際的代碼中復(fù)雜的 多,包含有很多條件判斷,有興趣的朋友可以自己去讀一下Qt的源代碼。
小結(jié):QT 父子與 QT 對(duì)象delete的內(nèi)容介紹完了,希望本文對(duì)你有所幫助。