Qt中内存泄露和半自动内存管理
Qt中幫程序員做了一些內(nèi)存回收的事情,但正因?yàn)檫@些反而讓對此不熟悉的人會屢屢犯錯(cuò)。
收錄一篇不錯(cuò)的文章:
在C++中學(xué)習(xí)過程中,我們都知道:
- delete 和 new 必須 配對使用(一 一對應(yīng)):delete少了,則內(nèi)存泄露,多了麻煩更大。
Qt作為C++的庫,顯然是不會違背C++的前述原則的。可是:
- 在Qt中,我們很多時(shí)候都瘋狂地用new,卻很少用delete,缺少的 delete 去哪兒了?!
注:本文暫不涉及智能指針(smart pointer)相關(guān)的東西,你可以考慮 Qt 智能指針學(xué)習(xí) 一文
Qt半自動的內(nèi)存管理
在Qt中,以下情況下你new出的對象你可以不用親自去delete (但你應(yīng)該清楚delete在何處被Qt調(diào)用的,怎么被調(diào)用的):
- QObject及其派生類的對象,如果其parent非0,那么其parent析構(gòu)時(shí)會析構(gòu)該對象(本文內(nèi)容圍繞這一點(diǎn)展開)
除此之外,有些類的對象可以接收設(shè)置一些特別的標(biāo)記,比如:
- QWidget及其派生類的對象,可以設(shè)置 Qt::WA_DeleteOnClose 標(biāo)志位(當(dāng)close時(shí)會析構(gòu)該對象)
- QAbstractAnimation派生類的對象,可以設(shè)置 QAbstractAnimation::DeleteWhenStopped
- QRunnable::setAutoDelete()
- MediaSource::setAutoDelete()
- ...
注意:這些用法會有些陷阱,請注意看本文最后的3個(gè)小例子。
在Qt中,最基礎(chǔ)和核心的類是:QObject 。它的魔力很大,本文只關(guān)注兩點(diǎn):
- 父子關(guān)系
- deleteLater
父子關(guān)系
在Qt中,每個(gè) QObject 內(nèi)部都有一個(gè)list,用來保存所有的 children,還有一個(gè)指針,保存自己的parent。當(dāng)它自己析構(gòu)時(shí),它會將自己從parent的列表中刪除,并且析構(gòu)掉所有的children。
- 注意:在 Qt 中,我們經(jīng)常會遇到
- 基類、派生類,或父類、子類。這是對于派生體系來說的,和在C++相關(guān)書中看到的完全一樣,與這的parent無關(guān)
- 父對象、子對象、父子關(guān)系。這是Qt中所特有的,也就是這兒的parent所引入的,與類的繼承關(guān)系無關(guān)
建立與解除
Q_INVOKABLE QObject::QObject ( QObject * parent = 0 )- 創(chuàng)建一個(gè)QObject對象時(shí),如果指定了父對象,它就會將自己添加到父對象的 children 列表中
- 當(dāng)一個(gè)QObject對象析構(gòu)時(shí),它會將自己從父對象的 children 列表中移除(parent非0的話)
- 通過該函數(shù),將自己從原父對象的children中刪除,添加到新parent的children列表中
注:這三個(gè)函數(shù)都是通過一個(gè)內(nèi)部私有函數(shù)來實(shí)現(xiàn)的,這就是
QObjectPrivate::setParent_helper(QObject *o)獲取父、子對象
每個(gè)QObject只有一個(gè)父對象:
QObject * QObject::parent () const子對象可以有多個(gè)
const QObjectList & QObject::children () const所以可以根據(jù)條件來查找嘍:
T QObject::findChild ( const QString & name = QString() ) const QList<T> QObject::findChildren ( const QString & name = QString() ) constdeleteLater
deleteLater 包含兩層意思了
- delete
- later
呵呵,似乎這是廢話哈。
刪除自己
在去年春節(jié)前的時(shí)候吧,有人對
obj-> deleteLater()會像下面一樣調(diào)用delete:
delete obj;感到不解。然后我寫了這樣一個(gè)C++例子:
class A {public:A(){}void deleteMe(){delete this;} };int main() {A * a = new A;a->deleteMe();return 0; }應(yīng)該不需要解釋吧
later
Qt 是事件驅(qū)動的,所以發(fā)送一個(gè)刪除事件到事件系統(tǒng)就可以啦:
void QObject::deleteLater() {QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete)); }事件循環(huán)稍后看到該事件就會將其派發(fā)會這個(gè)widget:
bool QObject::event(QEvent *e) {switch (e->type()) { ...case QEvent::DeferredDelete:...一些例子
無關(guān)痛癢?
很簡短、很熟悉的一個(gè)例子是不?但是 如果你發(fā)現(xiàn)對象的析構(gòu)函數(shù)始終不被成功調(diào)用,會有什么感覺?
#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 一書的第一個(gè)例子。我們注意到這兒的 label 既沒有指定parent,也沒有對其調(diào)用delete。
所以,這兒會造成內(nèi)存泄露。
書中解釋說,對于這種小例子,這點(diǎn)內(nèi)存泄露不算什么。不清楚官方這個(gè)例子的意圖是什么,或許是一開始就讓大家用指針吧。
三種改進(jìn)方式
- 分配對象到stack而不是heap中
- 設(shè)置標(biāo)志位,這樣,當(dāng)我們點(diǎn)擊關(guān)閉按鈕時(shí),close()函數(shù)將會調(diào)用deleteLater
- 動手調(diào)用delete(不就是少了一個(gè)么,我們補(bǔ)上還不行么)
單獨(dú)列一個(gè)吧
強(qiáng)化一下對前一個(gè)例子的了解
#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(); }運(yùn)行正常,退出時(shí)會崩潰,因?yàn)閘abel被close時(shí),將會 delete 這兒label對象,但label對象卻不是通過new分配到heap中的。
為了使得用戶減少自己顯式使用delete,Qt將delete隱藏的比較深。這樣一來,不使用new為對象分配空間時(shí),反倒需要多多小心了。
隱蔽很深?
看個(gè)小例子:這個(gè)程序退出時(shí)會直接崩潰。
#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(); }- 問題出在哪兒呢?因?yàn)橥顺鰰r(shí),w 比 label 先被析構(gòu),當(dāng) w 被析構(gòu)時(shí),會刪除chilren列表中的對象,也就是這兒的 label。但 label 卻不是通過new分配在heap中,而是在stack中,可想而知,delete 一個(gè)再stack中的對象會怎么樣了。相當(dāng)于
- 兩種改進(jìn)辦法:
- 一是,將label分配到heap中
- 再一種就是,確保label先于其parent被析構(gòu)(調(diào)整一下順序),這樣,label析構(gòu)時(shí)將自己從父對象的列表中移除自己,w析構(gòu)時(shí),children列表中就不會有分配在stack中的對象了。
Qt 對象的父子關(guān)系的引入,簡化了我們對內(nèi)存的管理,但是,由于它會在你不太注意的地方調(diào)用 delete,所以,使用時(shí)還是要當(dāng)心。
原文鏈接:http://blog.csdn.net/dbzhang800/article/details/6300025
總結(jié)
以上是生活随笔為你收集整理的Qt中内存泄露和半自动内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算符“.*”和“-*”,用于“成员指针”
- 下一篇: STM32-使用函数指针时莫名复位问题原