QT父子與QT物件delete
轉自:http://wenku.baidu.com/view/048b8f85bceb19e8b8f6ba64.html
很多C/C++初學者常犯的一個錯誤就是,使用malloc、new分配了一塊記憶體卻忘記釋放,導致記憶體洩漏。Qt的物件模型提供了一種Qt物件之間的父子關係,當很多個物件都按一定次序建立起來這種父子關係的時候,就組織成了一顆樹。當delete一個父物件的時候,Qt的物件模型機制保證了會自動的把 它的所有子物件,以及孫物件,等等,全部delete,從而保證不會有記憶體洩漏的情況發生。
任何事情都有正反兩面作用,這種機制看上去挺好,但是卻會對很多Qt的初學者造成困擾,我經常給別人回答的問題是:1,new了一個Qt物件之後,在什麼情況下應該delete它?2,Qt的解構函式是不是有bug?3,為什麼正常delete一個Qt物件卻會產生segment fault?等等諸如此類的問題,這篇文章就是針對這個問題的詳細解釋。
在每一個Qt物件中,都有一個連結串列,這個連結串列儲存有它所有子物件的指標。當建立一個新的Qt物件的時候,如果把另外一個Qt物件指定為這個物件的父物件, 那麼父物件就會在它的子物件連結串列中加入這個子物件的指標。另外,對於任意一個Qt物件而言,在其生命週期的任何時候,都還可以通過setParent函式 重新設定它的父物件。當一個父物件在被delete的時候,它會自動的把它所有的子物件全部delete。當一個子物件在delete的時候,會把它自己 從它的父物件的子物件連結串列中刪除。
QWidget是所有在螢幕上顯示出來的介面物件的基類,它擴充套件了Qt物件的父子關係。一個Widget物件也就自然的成為其父Widget物件的子 Widget,並且顯示在它的父Widget的座標系統中。例如,一個對話方塊(dialog)上的按鈕(button)應該是這個對話方塊的子 Widget。
關於Qt物件的new和delete,下面我們舉例說明。
例如,下面這一段程式碼是正確的:
1 intmain()
2 {
3 QObject*objParent = new QObject(NULL);
4 QObject*objChild = new QObject(objParent);
5 QObject*objChild2 = new QObject(objParent);
6 deleteobjParent;
7 }
我們用一張圖來描述這三個物件之間的關係:
在上述程式碼片段中,objParent是objChild的父物件,在objParent物件中有一個子物件連結串列,這個連結串列中儲存它所有子物件的指標,在 這裡,就是儲存了objChild和objChild2的指標。在程式碼的結束部分,就只有delete了一個物件objParent,在 objParent物件的解構函式會遍歷它的子物件連結串列,並且把它所有的子物件(objChild和objChild2)一一刪除。所以上面這段程式碼是安 全的,不會造成記憶體洩漏。
如果我們把上面這段程式碼改成這樣,也是正確的:
8 intmain()
9 {
10 QObject*objParent = new QObject(NULL);
11 QObject*objChild = new QObject(objParent);
12 QObject*objChild2 = new QObject(objParent);
13 deleteobjChild;
14 deleteobjParent;
15 }
在這段程式碼中,我們就只看一下和上一段程式碼不一樣的地方,就是在delete objParent物件之前,先delete objChild物件。在delete objChild物件的時候,objChild物件會自動的把自己從objParent物件的子物件連結串列中刪除,也就是說,在objChild物件被 delete完成之後,objParent物件就只有一個子物件(objChild2)了。然後在delete objParent物件的時候,會自動把objChild2物件也delete。所以,這段程式碼也是安全的。
Qt的這種設計對某些除錯工具來說卻是不友好的,比如valgrind。比如上面這段程式碼,valgrind工具在分析程式碼的時候,就會認為objChild2物件沒有被正確的delete,從而會報告說,這段程式碼存在記憶體洩漏。哈哈,我們知道,這個報告是不對的。
我們在看一看這一段程式碼:
16 intmain()
17 {
18 QWidgetwindow;
19 QPushButtonquit("Exit", &window);
20 }
在這段程式碼中,我們建立了兩個widget物件,第一個是window,第二個是quit,他們都是Qt物件,因為QPushButton是從 QWidget派生出來的,而QWidget是從QObject派生出來的。這兩個物件之間的關係是,window物件是quit物件的父物件,由於他們 都會被分配在棧(stack)上面,那麼quit物件是不是會被析構兩次呢?我們知道,在一個函式體內部宣告的變數,在這個函式退出的時候就會被析構,那 麼在這段程式碼中,window和quit兩個物件在函式退出的時候解構函式都會被呼叫。那麼,假設,如果是window的解構函式先被呼叫的話,它就會去 delete quit物件;然後quit的解構函式再次被呼叫,程式就出錯了。事實情況不是這樣的,C++標準規定,本地物件的解構函式的呼叫順序與他們的構造順序相反(轉者注:這個狠重要,所以寫區域性變數時要先寫父物件再寫子物件)。那麼在這段程式碼中,這就是quit物件的解構函式一定會比window物件的解構函式先被呼叫,所以,在window物件析構的時候,quit物件已經不存在了,不會被析構兩次。
如果我們把程式碼改成這個樣子,就會出錯了,對照前面的解釋,請你自己來分析一下吧。
21 intmain()
22 {
23 QPushButtonquit("Exit");
24 QWidgetwindow;
25 quit.setParent(&window);
26 }
但是我們自己在寫程式的時候,也必須重點注意一項,千萬不要delete子物件兩次,就像前面這段程式碼那樣,程式肯定就crash了。
最後,讓我們來結合Qt source code,來看看這parent/child關係是如何實現的。
在本專欄文章的第一部分“物件資料儲存”,我們說到過,所有Qt物件的私有資料成員的基類是QObjectData類,這個類的定義如下:
27 typedefQList<QObject*> QObjectList;
28 classQObjectData
29 {
30 public:
31 QObject*parent;
32 QObjectListchildren;
33 //忽略其它成員定義
34 };
我們可以看到,在這裡定義了指向parent的指標,和儲存子物件的列表。其實,把一個物件設定成另一個物件的父物件,無非就是在操作這兩個資料。把子對 象中的這個parent變數設定為指向其父物件;而在父物件的children列表中加入子物件的指標。當然,我這裡說的非常簡單,在實際的程式碼中複雜的 多,包含有很多條件判斷,有興趣的朋友可以自己去讀一下Qt的原始碼。
相關文章
- Qt遍歷子物件QT物件
- Qt - Qt Creator下載與安裝QT
- 什麼是qt,QT Creator, QT SDK, QT DesignerQT
- C++ Qt開發:Qt的安裝與配置C++QT
- OSG 與QT 結合QT
- qt呼叫js,js呼叫qtQTJS
- Qt:QT右鍵選單QT
- QTQT
- Qt之訊號與槽QT
- 【轉】qt-vs-addin:Qt4和Qt5之VS外掛如何共存與使用QT
- Qt入門(11)——Qt外掛QT
- Qt Creator匯入不同Qt版本QT
- Qt原始碼閱讀(三) 物件樹管理QT原始碼物件
- Qt入門(12)——Qt國際化QT
- Qt入門(13)——Qt的呼叫退出QT
- Qt入門(20)——Qt模組簡介QT
- MFC,QT與WinForm,WPF簡介QTORM
- Qt 繪圖與動畫系統QT繪圖動畫
- Qt FontQT
- Qt MetaTypeInterfaceQT
- Qt原始碼解析——元物件系統熱身QT原始碼物件
- Qt中對QString 型別物件的分割QT型別物件
- 【QT】 Qt多執行緒的“那些事”QT執行緒
- Qt繪圖淺析與例項QT繪圖
- Qt構建與解析Json示例QTJSON
- QT槽函式獲取訊號傳送物件QT函式物件
- QT Creator/QT Designer佈局自適應QT
- [Android] Qt安卓教程(2):移植Qt到安卓AndroidQT安卓
- 靜態編譯Qt5.4.1和Qt WebKit編譯QTWebKit
- qt亂碼QT
- Qt Creater 2QT
- QT中namespaceQTnamespace
- qt多文件QT
- Qt乾糧QT
- QT 播放 FLASHQT
- Qt undefined reference to ***QTUndefined
- Qt ——塞班之死QT
- Qt Phonon教程QT