Qt 半內存管理

在C++中學習過程當中,咱們都知道: delete 和 new 必須配對使用(一 一對應):delete少了,則內存泄露,多了麻煩更大。 Qt做爲C++的庫,顯然是不會違背C++的前述原則的。但是: 在Qt中,咱們不少時候都瘋狂地用new,卻不多用delete,缺乏的 delete 去哪兒了?!app


注:本文暫不涉及智能指針(smart pointer)相關的東西,你能夠考慮 Qt 智能指針學習 一文 Qt半自動的內存管理函數

在Qt中,如下狀況下你new出的對象你能夠不用親自去delete (但你應該清楚delete在何處被Qt調用的,怎麼被調用的):post

QObject及其派生類的對象,若是其parent非0,那麼其parent析構時會析構該對象(本文內容圍繞這一點展開 )學習

除此以外,有些類的對象能夠接收設置一些特別的標記,好比:ui

QWidget及其派生類的對象,能夠設置 Qt::WA_DeleteOnClose 標誌位(當close時會析構該對象)this

QAbstractAnimation派生類的對象,能夠設置spa

QAbstractAnimation::DeleteWhenStopped指針

QRunnable::setAutoDelete()code

MediaSource::setAutoDelete() ... 注意:這些用法會有些陷阱 ,請注意看本文最後的3個小例子。 在Qt中,最基礎和核心的類是:QObject 。它的魔力很大,本文只關注兩點: 父子關係 deleteLater對象

父子關係

在Qt中,每一個 QObject 內部都有一個list,用來保存全部的 children,還有一個指針,保存本身的parent。當它本身析構時,它會將本身從parent的列表中刪除,而且析構掉全部的children。

注意:在 Qt 中,咱們常常會遇到基類、派生類,或父類、子類。

這是對於派生體系來講的,和在C++相關書中看到的徹底同樣,與這的parent無關父對象、子對象、父子關係。 這是Qt中所特有的,也就是這兒的parent所引入的,與類的繼承關係無關 創建與解除

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Q_INVOKABLE QObject::QObject ( QObject * parent = 0 )
//建立一個QObject對象時,若是指定了父對象,它就會將本身添加到父對象的 children 列表中
QObject::~QObject () [ virtual ]
//當一個QObject對象析構時,它會將本身從父對象的 children 列表中移除(parent非0的話)
void QObject::setParent ( QObject * parent )
//經過該函數,將本身從原父對象的children中刪除,添加到新parent的children列表中
//注:這三個函數都是經過一個內部私有函數來實現的,這就是
QObjectPrivate::setParent_helper(QObject *o)
//獲取父、子對象
 
//每一個QObject只有一個父對象:
QObject * QObject::parent () const
//子對象能夠有多個
const QObjectList & QObject::children () const
//因此能夠根據條件來查找嘍:
T QObject::findChild ( const QString & name = QString() ) const
QList<T> QObject::findChildren ( const QString & name = QString() ) const

deleteLater

deleteLater 包含兩層意思了 delete later 呵呵,彷佛這是廢話哈。 刪除本身

在去年春節前的時候吧,有人對

obj-> deleteLater()

會像下面同樣調用delete:

delete obj;

感到不解。而後我寫了這樣一個C++例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{
   public :
   A(){}
   void deleteMe()
   {
       delete this ;
   }
};
 
int main()
{
   A * a = new A;
   a->deleteMe();
   return 0;
}

應該不須要解釋吧 later

Qt 是事件驅動的,因此發送一個刪除事件到事件系統就能夠啦:

?
1
2
3
4
void QObject::deleteLater()
{
     QCoreApplication::postEvent( this , new QEvent(QEvent::DeferredDelete));
}

事件循環稍後看到該事件就會將其派發會這個widget:

?
1
2
3
4
5
6
7
8
9
bool QObject::event(QEvent *e)
{
     switch (e->type())
     {
       case QEvent::DeferredDelete:
          ...
          break ;
     }
}

一些例子 無關痛癢? 很簡短、很熟悉的一個例子是不?可是 若是你發現對象的析構函數始終不被成功調用 ,會有什麼感受?

?
1
2
3
4
5
6
7
8
9
#include <QApplication>
#include <QLabel>
int main( int argc, char *argv[])
{
     QApplication app(argc, argv);
     QLabel *label = new QLabel( "Hello Qt!" );
     label->show();
     return app.exec();
}

這是 C++ GUI Programming with Qt 4 一書的第一個例子。咱們注意到這兒的 label 既沒有指定parent,也沒有對其調用delete。 因此,這兒會形成內存泄露。 書中解釋說,對於這種小例子,這點內存泄露不算什麼。不清楚官方這個例子的意圖是什麼,或許是一開始就讓你們用指針吧。 三種改進方式 分配對象到stack而不是heap中

?
1
2
3
4
5
6
7
8
9
QLabel label( "Hello Qt!" );
label.show();
 
//設置標誌位,這樣,當咱們點擊關閉按鈕時,close()函數將會調用deleteLater
label->setAttribute(Qt::WA_DeleteOnClose);
//動手調用delete(不就是少了一個麼,咱們補上還不行麼)
int ret = app.exec();
delete label;
return ret;

單獨列一個吧 強化一下對前一個例子的瞭解

?
1
2
3
4
5
6
7
8
9
10
#include <QApplication>
#include <QLabel>
int main( int argc, char *argv[])
{
     QApplication app(argc, argv);
     QLabel label( "Hello Qt!" );
     label.show();
     label.setAttribute(Qt::WA_DeleteOnClose);
     return app.exec();
}

運行正常,退出時會崩潰 ,由於label被close時,將會 delete 這兒label對象,但label對象卻不是經過new分配到heap中的。 爲了使得用戶減小本身顯式使用delete,Qt將delete隱藏的比較深。這樣一來,不使用new爲對象分配空間時,反倒須要多多當心了。 隱蔽很深?

看個小例子:這個程序退出時會直接崩潰 。

?
1
2
3
4
5
6
7
8
9
10
#include <QtGui>
int main( int argc, char * argv[])
{
    QApplication app(argc, argv);
    QLabel label(tr "Hello Qt!" );
    QWidget w;
    label.setParent(&w);
    w.show();
    return app.exec();
}

問題出在哪兒呢?由於退出時,w 比 label 先被析構,當 w 被析構時,會刪除chilren列表中的對象,也就是這兒的 label。但 label 卻不是經過new分配在heap中,而是在stack中,可想而知,delete 一個再stack中的對象會怎麼樣了。至關於

?
1
2
QLabel label();
delete &label;

兩種改進辦法: 一是,將label分配到heap中

?
1
2
QLabel *label = new QLabel( "Hello Qt!" );
label.setParent(&w)

再一種就是,確保label先於其parent被析構(調整一下順序),這樣,label析構時將本身從父對象的列表中移除本身,w析構時,children列表中就不會有分配在stack中的對象了。

?
1
2
QWidget w;
QLabel label(tr "Hello Qt!" );

Qt 對象的父子關係的引入,簡化了咱們對內存的管理,可是,因爲它會在你不太注意的地方調用 delete,因此,使用時仍是要小心。

相關文章
相關標籤/搜索