強類型語言在創建對象時總會顯式或隱式地包含對象的類型信息。也就是說,強類型語言在分配對象內存空間時,總會關聯上對象的類型。相比之下,弱類型 語言則不會這樣做。在分配了內存空間之後,有兩種方法釋放空間:手工釋放,或者是使用垃圾收集器。C++ 要求開發者手工釋放內存空間。這樣做的好處是,開發者對內存有完全的控制能力,知道什麼時候釋放比較合適。Java 則使用垃圾收集器。它在後臺會有一個線程根據一定的算法不停地查看哪些對象已經不被使用,可以被回收。這樣做則可以將開發者從底層實現中解放出來,只需關
注於業務邏輯。
在C++中學習過程中,我們都知道:
- delete 和 new 必須 配對使用(一 一對應):delete少了,則內存泄露,多了麻煩更大。
Qt作爲C++的庫,顯然是不會違背C++的前述原則的。可是:
- 在Qt中,我們很多時候都瘋狂地用new,卻很少用delete,缺少的 delete 去哪兒了?!
注:本文暫不涉及智能指針(smart pointer)相關的東西,你可以考慮 Qt 智能指針學習 一文
Qt半自動的內存管理
在Qt中,以下情況下你new出的對象你可以不用 親自去delete (但你應該清楚delete在何處被Qt調用的,怎麼被調用的):
- QObject及其派生類的對象,如果其parent非0,那麼其parent析構時會析構該對象(本文內容圍繞這一點展開 )
除此之外,有些類的對象可以接收設置一些特別的標記,比如:
- QWidget及其派生類的對象,可以設置 Qt::WA_DeleteOnClose 標誌位(當close時會析構該對象)
- QAbstractAnimation派生類的對象,可以設置 QAbstractAnimation::DeleteWhenStopped
- QRunnable::setAutoDelete()
- MediaSource::setAutoDelete()
- ...
注意:這些用法會有些陷阱 ,請注意看本文最後的3個小例子。
在Qt中,最基礎和核心的類是:QObject 。它的魔力很大,本文只關注兩點:
- 父子關係
- deleteLater
父子關係
在Qt中,每個 QObject 內部都有一個list,用來保存所有的 children,還有一個指針,保存自己的parent。當它自己析構時,它會將自己從parent的列表中刪除,並且析構掉所有的children。
- 注意:在 Qt 中,我們經常會遇到
- 基類、派生類,或父類、子類。 這是對於派生體系來說的,和在C++相關書中看到的完全一樣,與這的parent無關
- 父對象、子對象、父子關係。 這是Qt中所特有的,也就是這兒的parent所引入的,與類的繼承關係無關
建立與解除
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++例子:
class A { public: A(){} void deleteMe() { delete this; } }; int main() { A * a = new A; a->deleteMe(); return 0; }
應該不需要解釋吧
later
Qt 是事件驅動的,所以發送一個刪除事件到事件系統就可以啦:
void QObject::deleteLater() { QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete)); }
事件循環稍後看到該事件就會將其派發會這個widget:
bool QObject::event(QEvent *e) { switch (e->type()) { ... case QEvent::DeferredDelete: ...
一些例子
無關痛癢?
很簡短、很熟悉的一個例子是不?但是 如果你發現對象的析構函數始終不被成功調用 ,會有什麼感覺?
#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中
QLabel label("Hello Qt!"); label.show();
- 設置標誌位,這樣,當我們點擊關閉按鈕時,close()函數將會調用deleteLater
label->setAttribute(Qt::WA_DeleteOnClose);
- 動手調用delete(不就是少了一個麼,我們補上還不行麼)
int ret = app.exec(); delete label; return ret;
單獨列一個吧
強化一下對前一個例子的瞭解
#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爲對象分配空間時,反倒需要多多小心了。
隱蔽很深?
看個小例子:這個程序退出時會直接崩潰 。
#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中的對象會怎麼樣了。相當於
QLabel label(); delete &label;
- 兩種改進辦法:
- 一是,將label分配到heap中
QLabel *label = new QLabel("Hello Qt!"); label.setParent(&w)
- 再一種就是,確保label先於其parent被析構(調整一下順序),這樣,label析構時將自己從父對象的列表中移除自己,w析構時,children列表中就不會有分配在stack中的對象了。
QWidget w; QLabel label(tr"Hello Qt!");
Qt 對象的父子關係的引入,簡化了我們對內存的管理,但是,由於它會在你不太注意的地方調用 delete,所以,使用時還是要當心。