Qt 記憶體管理機制
轉自:http://devbean.blog.51cto.com/448512/526734
這篇文章首先釋出於我的主頁
http://www.devbean.info,以後也會直接釋出在那裡。現在有 Flex 4 的一篇和 《從 C++ 到 Objective-C》系列,感謝大家支援!
強型別語言在建立物件時總會顯式或隱式地包含物件的型別資訊。也就是說,強型別語言在分配物件記憶體空間時,總會關聯上物件的型別。相比之下,弱型別 語言則不會這樣做。在分配了記憶體空間之後,有兩種方法釋放空間:手工釋放,或者是使用垃圾收集器。C++ 要求開發者手工釋放記憶體空間。這樣做的好處是,開發者對記憶體有完全的控制能力,知道什麼時候釋放比較合適。Java 則使用垃圾收集器。它在後臺會有一個執行緒根據一定的演算法不停地檢視哪些物件已經不被使用,可以被回收。這樣做則可以將開發者從底層實現中解放出來,只需關 注於業務邏輯。
本文關注於 Qt 的記憶體管理,這裡會使用 Qt 的機制,來實現一個簡單的垃圾回收器。
C++ 記憶體管理機制
C++ 要求開發者自己管理記憶體。有三種策略:
- 讓建立的物件自己 delete 自己的子物件(這裡所說的子物件,是指物件的屬性,而不是子類,以下類似);
- 讓最後一個物件處理 delete;
- 不管記憶體。
最後一種通常成為“記憶體洩漏”,被認為是一種 bug。所以,我們現在就是要選出前面兩種哪一種更合適一些。有時候,delete 建立的物件要比 delete 它的所有子物件簡單得多;有時候,找出最後一個物件也是相當困難的。
Qt 記憶體管理機制
Qt 在內部能夠維護物件的層次結構。對於可視元素,這種層次結構就是子元件與父元件的關係;對於非可視元素,則是一個物件與另一個物件的從屬關係。在 Qt 中,刪除父物件會將其子物件一起刪除。這有助於減少 90% 的記憶體問題,形成一種類似垃圾回收的機制。
QPointer
QPointer 是一個模板類。它很類似一個普通的指標,不同之處在於,QPointer 可以監視動態分配空間的物件,並且在物件被 delete 的時候及時更新。
- // QPointer 表現類似普通指標
- QDate *mydate = new QDate(QDate::currentDate());
- QPointer mypointer = mydata;
- mydate->year(); // -> 2005
- mypointer->year(); // -> 2005
- // 當物件 delete 之後,QPointer 會有不同的表現
- delete mydate;
- if(mydate == NULL)
- printf("clean pointer");
- else
- printf("dangling pointer");
- // 輸出 dangling pointer
- if(mypointer.isNull())
- printf("clean pointer");
- else
- printf("dangling pointer");
- // 輸出 clean pointer
注意上面的程式碼。一個原始指標 delete 之後,其值不會被設定為 NULL,因此會成為野指標。但是,QPionter 沒有這個問題。
QObjectCleanupHandler
Qt 物件清理器是實現自動垃圾回收的很重要的一部分。它可以註冊很多子物件,並在自己刪除的時候自動刪除所有子物件。同時,它也可以識別出是否有子物件被刪 除,從而將其從它的子物件列表中刪除。這個類可以用於不在同一層次中的類的清理操作,例如,當按鈕按下時需要關閉很多視窗,由於視窗的 parent 屬性不可能設定為別的視窗的 button,此時使用這個類就會相當方便。
- // 建立例項
- QObjectCleanupHandler *cleaner = new QObjectCleanupHandler;
- // 建立視窗
- QPushButton *w = new QPushButton("Remove Me");
- w->show();
- // 註冊第一個按鈕
- cleaner->add(w);
- // 如果第一個按鈕點選之後,刪除自身
- connect(w, SIGNAL(clicked()), w, SLOT(deleteLater()));
- // 建立第二個按鈕,注意,這個按鈕沒有任何動作
- w = new QPushButton("Nothing");
- cleaner->add(w);
- w->show();
- // 建立第三個按鈕,刪除所有
- w = new QPushButton("Remove All");
- cleaner->add(w);
- connect(w, SIGNAL(clicked()), cleaner, SLOT(deleteLater()));
- w->show();
在上面的程式碼中,建立了三個僅有一個按鈕的視窗。第一個按鈕點選後,會刪除掉自己(通過 deleteLater() 槽),此時,cleaner 會自動將其從自己的列表中清除。第三個按鈕點選後會刪除 cleaner,這樣做會同時刪除掉所有未關閉的視窗。
Qt 垃圾收集
隨著物件變得越來越複雜,很多地方都要使用這個物件的時候,什麼時候作 delete 操作很難決定。好在 Qt 對所有繼承自 QObject 的類都有很好的垃圾收集機制。垃圾收集有很多種實現方法,最簡單的是引用計數,還有一種是儲存所有物件。下面我們將詳細講解這兩種實現方法。
引用計數
應用計數是最簡單的垃圾回收實現:每建立一個物件,計數器加 1,每刪除一個則減 1。
- class CountedObject
- {
- public:
- CountedObject()
- {
- ctr=0;
- }
- void attach()
- {
- ctr++;
- }
- void detach()
- {
- ctr--;
- if(ctr <= 0)
- delete this;
- }
- private:
- int ctr;
- };
每一個子物件在建立之後都應該呼叫 attach() 函式,使計數器加 1,刪除的時候則應該呼叫 detach() 更新計數器。不過,這個類很原始,沒有使用 Qt 方便的機制。下面我們給出一個 Qt 版本的實現:
- class CountedObject : public QObject
- {
- Q_OBJECT
- public:
- CountedObject()
- {
- ctr=0;
- }
- void attach(QObject *obj)
- {
- ctr++;
- connect(obj, SIGNAL(destroyed(QObject*)), SLOT(detach()));
- }
- public slots:
- void detach()
- {
- ctr--;
- if(ctr <= 0)
- delete this;
- }
- private:
- int ctr;
- };
我們利用 Qt 的訊號槽機制,在物件銷燬的時候自動減少計數器的值。但是,我們的實現並不能防止物件建立的時候呼叫了兩次 attach()。
記錄所有者
更合適的實現是,不僅僅記住有幾個物件持有引用,而且要記住是哪些物件。例如:
- class CountedObject : public QObject
- {
- public:
- CountedObject()
- {
- }
- void attach(QObject *obj)
- {
- // 檢查所有者
- if(obj == 0)
- return;
- // 檢查是否已經新增過
- if(owners.contains(obj))
- return;
- // 註冊
- owners.append(obj);
- connect(obj, SIGNAL(destroyed(QObject*)), SLOT(detach(QObject*)));
- }
- public slots:
- void detach(QObject *obj)
- {
- // 刪除
- owners.removeAll(obj);
- // 如果最後一個物件也被 delete,刪除自身
- if(owners.size() == 0)
- delete this;
- }
- private:
- QList owners;
- };
現在我們的實現已經可以做到防止一個物件多次呼叫 attach() 和 detach() 了。然而,還有一個問題是,我們不能保證物件一定會呼叫 attach() 函式進行註冊。畢竟,這不是 C++ 內建機制。有一個解決方案是,重定義 new 運算子(這一實現同樣很複雜,不過可以避免出現有物件不呼叫 attach() 註冊的情況)。
本文來自 DevBean's World:http://www.devbean.info。
轉載時請標明文章原始出處:http://www.devbean.info/2011/03/qt_memory_management/。
本文出自 “豆子空間” 部落格,請務必保留此出處http://devbean.blog.51cto.com/448512/526734
相關文章
- jvm記憶體管理機制JVM記憶體
- javaScript 記憶體管理機制JavaScript記憶體
- Java記憶體管理機制Java記憶體
- 【記憶體管理】頁面分配機制記憶體
- 記憶體管理機制的發展記憶體
- 淺析java記憶體管理機制Java記憶體
- Python如何管理記憶體?記憶體分配機制是什麼?Python記憶體
- Python記憶體管理機制-《原始碼解析》Python記憶體原始碼
- android記憶體管理機制與優化Android記憶體優化
- JVM自動記憶體管理機制 二JVM記憶體
- Java的記憶體管理機制之記憶體區域劃分Java記憶體
- 一文洞悉JVM記憶體管理機制JVM記憶體
- 深度學習 Caffe 記憶體管理機制理解深度學習記憶體
- javascript的垃圾回收機制和記憶體管理JavaScript記憶體
- Objective-C中的記憶體管理機制Object記憶體
- V8記憶體管理及垃圾回收機制記憶體
- js記憶體回收機制JS記憶體
- [譯] 通過垃圾回收機制理解 JavaScript 記憶體管理JavaScript記憶體
- Android彈藥庫——記憶體管理機制與程式模型Android記憶體模型
- 要點提煉| 理解JVM之記憶體管理機制JVM記憶體
- AntDB記憶體管理之記憶體上下文之記憶體上下文機制是怎麼實現的記憶體
- 記憶體管理 記憶體管理概述記憶體
- QT之共享記憶體QT記憶體
- Qt共享記憶體QSharedMemoryQT記憶體
- php原始碼02 -基本變數與記憶體管理機制PHP原始碼變數記憶體
- JVM 自動記憶體管理機制及 GC 演算法JVM記憶體GC演算法
- jvm:記憶體模型、記憶體分配及GC垃圾回收機制JVM記憶體模型GC
- 深入理解Java虛擬機器筆記-自動記憶體管理機制Java虛擬機筆記記憶體
- 深入理解Java虛擬機器(自動記憶體管理機制)Java虛擬機記憶體
- Java程式執行記憶體機制Java記憶體
- Redis 記憶體淘汰機制詳解Redis記憶體
- 關於JavaScript的記憶體機制JavaScript記憶體
- Java記憶體模型,垃圾回收機制,常用記憶體命令及工具Java記憶體模型
- Linux核心筆記002 - i386 的頁式記憶體管理機制Linux筆記記憶體
- 自動記憶體管理機制_執行時資料區域記憶體
- 記憶體管理篇——實體記憶體的管理記憶體
- 【記憶體管理】記憶體佈局記憶體
- Redis的記憶體和實現機制Redis記憶體
- 建立快取記憶體機制-java版快取記憶體Java