Qt 內存管理機制

這篇文章首先發佈於個人主頁 http://www.devbean.info,之後也會直接發佈在那裏。如今有 Flex 4 的一篇和 《從 C++ 到 Objective-C》系列,感謝你們支持!
算法

強類型語言在建立對象時總會顯式或隱式地包含對象的類型信息。也就是說,強類型語言在分配對象內存空間時,總會關聯上對象的類型。相比之下,弱類型 語言則不會這樣作。在分配了內存空間以後,有兩種方法釋放空間:手工釋放,或者是使用垃圾收集器。C++ 要求開發者手工釋放內存空間。這樣作的好處是,開發者對內存有徹底的控制能力,知道何時釋放比較合適。Java 則使用垃圾收集器。它在後臺會有一個線程根據必定的算法不停地查看哪些對象已經不被使用,能夠被回收。這樣作則能夠將開發者從底層實現中解放出來,只需關 注於業務邏輯。app

本文關注於 Qt 的內存管理,這裏會使用 Qt 的機制,來實現一個簡單的垃圾回收器。ide

C++ 內存管理機制

C++ 要求開發者本身管理內存。有三種策略:函數

  1. 讓建立的對象本身 delete 本身的子對象(這裏所說的子對象,是指對象的屬性,而不是子類,如下相似);
  2. 讓最後一個對象處理 delete;
  3. 無論內存。

最後一種一般成爲「內存泄漏」,被認爲是一種 bug。因此,咱們如今就是要選出前面兩種哪種更合適一些。有時候,delete 建立的對象要比 delete 它的全部子對象簡單得多;有時候,找出最後一個對象也是至關困難的。this

Qt 內存管理機制

Qt 在內部可以維護對象的層次結構。對於可視元素,這種層次結構就是子組件與父組件的關係;對於非可視元素,則是一個對象與另外一個對象的從屬關係。在 Qt 中,刪除父對象會將其子對象一塊兒刪除。這有助於減小 90% 的內存問題,造成一種相似垃圾回收的機制。spa

QPointer

QPointer 是一個模板類。它很相似一個普通的指針,不一樣之處在於,QPointer 能夠監視動態分配空間的對象,而且在對象被 delete 的時候及時更新。線程

  
  
           
  
  
  1. // QPointer 表現相似普通指針 
  2. QDate *mydate = new QDate(QDate::currentDate()); 
  3. QPointer mypointer = mydata; 
  4. mydate->year();    // -> 2005 
  5. mypointer->year(); // -> 2005 
  6.   
  7. // 當對象 delete 以後,QPointer 會有不一樣的表現 
  8. delete mydate; 
  9.   
  10. if(mydate == NULL) 
  11.     printf("clean pointer"); 
  12. else 
  13.     printf("dangling pointer"); 
  14. // 輸出 dangling pointer 
  15.   
  16. if(mypointer.isNull()) 
  17.     printf("clean pointer"); 
  18. else 
  19.     printf("dangling pointer"); 
  20. // 輸出 clean pointer 

注意上面的代碼。一個原始指針 delete 以後,其值不會被設置爲 NULL,所以會成爲野指針。可是,QPionter 沒有這個問題。指針

QObjectCleanupHandler

Qt 對象清理器是實現自動垃圾回收的很重要的一部分。它能夠註冊不少子對象,並在本身刪除的時候自動刪除全部子對象。同時,它也能夠識別出是否有子對象被刪 除,從而將其從它的子對象列表中刪除。這個類能夠用於不在同一層次中的類的清理操做,例如,當按鈕按下時須要關閉不少窗口,因爲窗口的 parent 屬性不可能設置爲別的窗口的 button,此時使用這個類就會至關方便。對象

  
  
           
  
  
  1. // 建立實例 
  2. QObjectCleanupHandler *cleaner = new QObjectCleanupHandler; 
  3. // 建立窗口 
  4. QPushButton *w = new QPushButton("Remove Me"); 
  5. w->show(); 
  6. // 註冊第一個按鈕 
  7. cleaner->add(w); 
  8. // 若是第一個按鈕點擊以後,刪除自身 
  9. connect(w, SIGNAL(clicked()), w, SLOT(deleteLater())); 
  10. // 建立第二個按鈕,注意,這個按鈕沒有任何動做 
  11. w = new QPushButton("Nothing"); 
  12. cleaner->add(w); 
  13. w->show(); 
  14. // 建立第三個按鈕,刪除全部 
  15. w = new QPushButton("Remove All"); 
  16. cleaner->add(w); 
  17. connect(w, SIGNAL(clicked()), cleaner, SLOT(deleteLater())); 
  18. w->show(); 

在上面的代碼中,建立了三個僅有一個按鈕的窗口。第一個按鈕點擊後,會刪除掉本身(經過 deleteLater() 槽),此時,cleaner 會自動將其從本身的列表中清除。第三個按鈕點擊後會刪除 cleaner,這樣作會同時刪除掉全部未關閉的窗口。繼承

Qt 垃圾收集

隨着對象變得愈來愈複雜,不少地方都要使用這個對象的時候,何時做 delete 操做很難決定。好在 Qt 對全部繼承自 QObject 的類都有很好的垃圾收集機制。垃圾收集有不少種實現方法,最簡單的是引用計數,還有一種是保存全部對象。下面咱們將詳細講解這兩種實現方法。

引用計數

應用計數是最簡單的垃圾回收實現:每建立一個對象,計數器加 1,每刪除一個則減 1。

  
  
           
  
  
  1. class CountedObject 
  2. public
  3.     CountedObject() 
  4.     { 
  5.         ctr=0; 
  6.     } 
  7.   
  8.     void attach() 
  9.     { 
  10.         ctr++; 
  11.     } 
  12.   
  13.     void detach() 
  14.     { 
  15.         ctr--; 
  16.         if(ctr <= 0) 
  17.             delete this
  18.     } 
  19. private
  20.     int ctr; 
  21. }; 

 

每個子對象在建立以後都應該調用 attach() 函數,使計數器加 1,刪除的時候則應該調用 detach() 更新計數器。不過,這個類很原始,沒有使用 Qt 方便的機制。下面咱們給出一個 Qt 版本的實現:

  
  
           
  
  
  1. class CountedObject : public QObject 
  2.     Q_OBJECT 
  3. public
  4.     CountedObject() 
  5.     { 
  6.         ctr=0; 
  7.     } 
  8.   
  9.     void attach(QObject *obj) 
  10.     { 
  11.         ctr++; 
  12.         connect(obj, SIGNAL(destroyed(QObject*)), SLOT(detach())); 
  13.     } 
  14.   
  15. public slots: 
  16.     void detach() 
  17.     { 
  18.         ctr--; 
  19.         if(ctr <= 0) 
  20.             delete this
  21.     } 
  22.   
  23. private
  24.     int ctr; 
  25. }; 

咱們利用 Qt 的信號槽機制,在對象銷燬的時候自動減小計數器的值。可是,咱們的實現並不能防止對象建立的時候調用了兩次 attach()。

記錄全部者

更合適的實現是,不只僅記住有幾個對象持有引用,並且要記住是哪些對象。例如:

  
  
           
  
  
  1. class CountedObject : public QObject 
  2. public
  3.     CountedObject() 
  4.     { 
  5.     } 
  6.   
  7.     void attach(QObject *obj) 
  8.     { 
  9.         // 檢查全部者 
  10.         if(obj == 0) 
  11.             return
  12.         // 檢查是否已經添加過 
  13.         if(owners.contains(obj)) 
  14.             return
  15.         // 註冊 
  16.         owners.append(obj); 
  17.         connect(obj, SIGNAL(destroyed(QObject*)), SLOT(detach(QObject*))); 
  18.     } 
  19.   
  20. public slots: 
  21.     void detach(QObject *obj) 
  22.     { 
  23.         // 刪除 
  24.         owners.removeAll(obj); 
  25.         // 若是最後一個對象也被 delete,刪除自身 
  26.         if(owners.size() == 0) 
  27.             delete this
  28.     } 
  29.   
  30. private
  31.     QList owners; 
  32. }; 

如今咱們的實現已經能夠作到防止一個對象屢次調用 attach() 和 detach() 了。然而,還有一個問題是,咱們不能保證對象必定會調用 attach() 函數進行註冊。畢竟,這不是 C++ 內置機制。有一個解決方案是,重定義 new 運算符(這一實現一樣很複雜,不過能夠避免出現有對象不調用 attach() 註冊的狀況)。


本文來自 DevBean's World: http://www.devbean.info
轉載時請標明文章原始出處:
http://www.devbean.info/2011/03/qt_memory_management/
相關文章
相關標籤/搜索