[z]Qt 内存管理机制
文章只是簡(jiǎn)要的介紹了Qt的內(nèi)存管理機(jī)制,對(duì)理解內(nèi)存管理比較有幫助
?
強(qiáng)類型語(yǔ)言在創(chuàng)建對(duì)象時(shí)總會(huì)顯式或隱式地包含對(duì)象的類型信息。也就是說,強(qiáng)類型語(yǔ)言在分配對(duì)象內(nèi)存空間時(shí),總會(huì)關(guān)聯(lián)上對(duì)象的類型。相比之下,弱類型語(yǔ)言則不會(huì)這樣做。在分配了內(nèi)存空間之后,有兩種方法釋放空間:手工釋放,或者是使用垃圾收集器。C++ 要求開發(fā)者手工釋放內(nèi)存空間。這樣做的好處是,開發(fā)者對(duì)內(nèi)存有完全的控制能力,知道什么時(shí)候釋放比較合適。Java 則使用垃圾收集器。它在后臺(tái)會(huì)有一個(gè)線程根據(jù)一定的算法不停地查看哪些對(duì)象已經(jīng)不被使用,可以被回收。這樣做則可以將開發(fā)者從底層實(shí)現(xiàn)中解放出來,只需關(guān)注于業(yè)務(wù)邏輯。
本文關(guān)注于 Qt 的內(nèi)存管理,這里會(huì)使用 Qt 的機(jī)制,來實(shí)現(xiàn)一個(gè)簡(jiǎn)單的垃圾回收器。
?
C++ 內(nèi)存管理機(jī)制
C++ 要求開發(fā)者自己管理內(nèi)存。有三種策略:
最后一種通常成為“內(nèi)存泄漏”,被認(rèn)為是一種 bug。所以,我們現(xiàn)在就是要選出前面兩種哪一種更合適一些。有時(shí)候,delete 創(chuàng)建的對(duì)象要比 delete 它的所有子對(duì)象簡(jiǎn)單得多;有時(shí)候,找出最后一個(gè)對(duì)象也是相當(dāng)困難的。
?
Qt 內(nèi)存管理機(jī)制
Qt 在內(nèi)部能夠維護(hù)對(duì)象的層次結(jié)構(gòu)。對(duì)于可視元素,這種層次結(jié)構(gòu)就是子組件與父組件的關(guān)系;對(duì)于非可視元素,則是一個(gè)對(duì)象與另一個(gè)對(duì)象的從屬關(guān)系。在 Qt 中,刪除父對(duì)象會(huì)將其子對(duì)象一起刪除。這有助于減少 90% 的內(nèi)存問題,形成一種類似垃圾回收的機(jī)制。
?
QPointer
QPointer 是一個(gè)模板類。它很類似一個(gè)普通的指針,不同之處在于,QPointer 可以監(jiān)視動(dòng)態(tài)分配空間的對(duì)象,并且在對(duì)象被 delete 的時(shí)候及時(shí)更新。
// QPointer 表現(xiàn)類似普通指針 QDate *mydate = new QDate(QDate::currentDate()); QPointer mypointer = mydata; mydate->year(); // -> 2005 mypointer->year(); // -> 2005// 當(dāng)對(duì)象 delete 之后,QPointer 會(huì)有不同的表現(xiàn) delete mydate;if(mydate == NULL)printf("clean pointer"); elseprintf("dangling pointer"); // 輸出 dangling pointerif(mypointer.isNull())printf("clean pointer"); elseprintf("dangling pointer"); // 輸出 clean pointer注意上面的代碼。一個(gè)原始指針 delete 之后,其值不會(huì)被設(shè)置為 NULL,因此會(huì)成為野指針。但是,QPionter 沒有這個(gè)問題。
?
QObjectCleanupHandler
Qt 對(duì)象清理器是實(shí)現(xiàn)自動(dòng)垃圾回收的很重要的一部分。它可以注冊(cè)很多子對(duì)象,并在自己刪除的時(shí)候自動(dòng)刪除所有子對(duì)象。同時(shí),它也可以識(shí)別出是否有子對(duì)象被刪除,從而將其從它的子對(duì)象列表中刪除。這個(gè)類可以用于不在同一層次中的類的清理操作,例如,當(dāng)按鈕按下時(shí)需要關(guān)閉很多窗口,由于窗口的 parent 屬性不可能設(shè)置為別的窗口的 button,此時(shí)使用這個(gè)類就會(huì)相當(dāng)方便。
// 創(chuàng)建實(shí)例 QObjectCleanupHandler *cleaner = new QObjectCleanupHandler; // 創(chuàng)建窗口 QPushButton *w = new QPushButton("Remove Me"); w->show(); // 注冊(cè)第一個(gè)按鈕 cleaner->add(w); // 如果第一個(gè)按鈕點(diǎn)擊之后,刪除自身 connect(w, SIGNAL(clicked()), w, SLOT(deleteLater())); // 創(chuàng)建第二個(gè)按鈕,注意,這個(gè)按鈕沒有任何動(dòng)作 w = new QPushButton("Nothing"); cleaner->add(w); w->show(); // 創(chuàng)建第三個(gè)按鈕,刪除所有 w = new QPushButton("Remove All"); cleaner->add(w); connect(w, SIGNAL(clicked()), cleaner, SLOT(deleteLater())); w->show();在上面的代碼中,創(chuàng)建了三個(gè)僅有一個(gè)按鈕的窗口。第一個(gè)按鈕點(diǎn)擊后,會(huì)刪除掉自己(通過 deleteLater() 槽),此時(shí),cleaner 會(huì)自動(dòng)將其從自己的列表中清除。第三個(gè)按鈕點(diǎn)擊后會(huì)刪除 cleaner,這樣做會(huì)同時(shí)刪除掉所有未關(guān)閉的窗口。
?
Qt 垃圾收集
隨著對(duì)象變得越來越復(fù)雜,很多地方都要使用這個(gè)對(duì)象的時(shí)候,什么時(shí)候作 delete 操作很難決定。好在 Qt 對(duì)所有繼承自 QObject 的類都有很好的垃圾收集機(jī)制。垃圾收集有很多種實(shí)現(xiàn)方法,最簡(jiǎn)單的是引用計(jì)數(shù),還有一種是保存所有對(duì)象。下面我們將詳細(xì)講解這兩種實(shí)現(xiàn)方法。
?
引用計(jì)數(shù)
應(yīng)用計(jì)數(shù)是最簡(jiǎn)單的垃圾回收實(shí)現(xiàn):每創(chuàng)建一個(gè)對(duì)象,計(jì)數(shù)器加 1,每刪除一個(gè)則減 1。
class CountedObject { public:CountedObject(){ctr=0;}void attach(){ctr++;}void detach(){ctr--;if(ctr <= 0)delete this;} private:int ctr; };每一個(gè)子對(duì)象在創(chuàng)建之后都應(yīng)該調(diào)用 attach() 函數(shù),使計(jì)數(shù)器加 1,刪除的時(shí)候則應(yīng)該調(diào)用 detach() 更新計(jì)數(shù)器。不過,這個(gè)類很原始,沒有使用 Qt 方便的機(jī)制。下面我們給出一個(gè) Qt 版本的實(shí)現(xiàn):
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 的信號(hào)槽機(jī)制,在對(duì)象銷毀的時(shí)候自動(dòng)減少計(jì)數(shù)器的值。但是,我們的實(shí)現(xiàn)并不能防止對(duì)象創(chuàng)建的時(shí)候調(diào)用了兩次 attach()。
記錄所有者
更合適的實(shí)現(xiàn)是,不僅僅記住有幾個(gè)對(duì)象持有引用,而且要記住是哪些對(duì)象。例如:
class CountedObject : public QObject { public:CountedObject(){}void attach(QObject *obj){// 檢查所有者if(obj == 0)return;// 檢查是否已經(jīng)添加過if(owners.contains(obj))return;// 注冊(cè)owners.append(obj);connect(obj, SIGNAL(destroyed(QObject*)), SLOT(detach(QObject*)));}public slots:void detach(QObject *obj){// 刪除owners.removeAll(obj);// 如果最后一個(gè)對(duì)象也被 delete,刪除自身if(owners.size() == 0)delete this;}private:QList owners; };現(xiàn)在我們的實(shí)現(xiàn)已經(jīng)可以做到防止一個(gè)對(duì)象多次調(diào)用 attach() 和 detach() 了。然而,還有一個(gè)問題是,我們不能保證對(duì)象一定會(huì)調(diào)用 attach() 函數(shù)進(jìn)行注冊(cè)。畢竟,這不是 C++ 內(nèi)置機(jī)制。有一個(gè)解決方案是,重定義 new 運(yùn)算符(這一實(shí)現(xiàn)同樣很復(fù)雜,不過可以避免出現(xiàn)有對(duì)象不調(diào)用 attach() 注冊(cè)的情況)。
?
原文:http://www.devbean.info/2011/03/qt_memory_management/
轉(zhuǎn)載于:https://www.cnblogs.com/waytofall/archive/2012/01/02/2309812.html
總結(jié)
以上是生活随笔為你收集整理的[z]Qt 内存管理机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 制作类似QQ截图软件
- 下一篇: 2012-01-10 自己写的基于jq