Contents
  1. 1. Qt半自动的内存管理
  2. 2. 父子关系
    1. 2.1. 建立与解除
    2. 2.2. 获取父、子对象
  3. 3. deleteLater
    1. 3.1. 删除自己
    2. 3.2. later
  4. 4. 一些例子
    1. 4.1. 无关痛痒
    2. 4.2. 单独列一个吧
    3. 4.3. 隐蔽很深?
  5. 5. 参考

【转】原创作品,允许转载。转载时请务必以超链接形式标明文章原始出处、作者信息和本声明,否则将追究法律责任。
http://blog.csdn.net/dbzhang800/article/details/6300025

在C++中学习过程中,我们都知道:

  • delete 和 new 必须 配对使用(一 一对应):delete少了,则内存泄露,多了麻烦更大。

Qt作为C++的库,显然是不会违背C++的前述原则的。可是:

  • 在Qt中,我们很多时候都疯狂地用new,却很少用delete,缺少的 delete 去哪儿了?!
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所引入的,与类的继承关系无关
建立与解除
1
Q_INVOKABLE QObject::QObject ( QObject * parent = 0 )

创建一个QObject对象时,如果指定了父对象,它就会将自己添加到父对象的 children 列表中

1
QObject::~QObject () [virtual]

当一个QObject对象析构时,它会将自己从父对象的 children 列表中移除(parent非0的话)

1
void QObject::setParent ( QObject * parent )

通过该函数,将自己从原父对象的children中删除,添加到新parent的children列表中

注:这三个函数都是通过一个内部私有函数来实现的,这就是

1
QObjectPrivate::setParent_helper(QObject *o)
获取父、子对象

每个QObject只有一个父对象:

1
QObject * QObject::parent () const

子对象可以有多个

1
const QObjectList & QObject::children () const

所以可以根据条件来查找喽:

1
2
T QObject::findChild ( const QString & name = QString() ) const
QList<T> QObject::findChildren ( const QString & name = QString() ) const
deleteLater

deleteLater 包含两层意思了

  • delete
  • later

呵呵,似乎这是废话哈。

删除自己

在去年春节前的时候吧,有人对

1
obj-> deleteLater()

会像下面一样调用delete:

1
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
bool QObject::event(QEvent *e)
{
switch (e->type()) {
...
case QEvent::DeferredDelete:
...
一些例子
无关痛痒

很简短、很熟悉的一个例子是不?但是 如果你发现对象的析构函数始终不被成功调用 ,会有什么感觉?

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
QLabel label("Hello Qt!");
label.show();
  • 设置标志位,这样,当我们点击关闭按钮时,close()函数将会调用deleteLater
1
label->setAttribute(Qt::WA_DeleteOnClose);
  • 动手调用delete(不就是少了一个么,我们补上还不行么)
1
2
3
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,所以,使用时还是要当心。

参考
Contents
  1. 1. Qt半自动的内存管理
  2. 2. 父子关系
    1. 2.1. 建立与解除
    2. 2.2. 获取父、子对象
  3. 3. deleteLater
    1. 3.1. 删除自己
    2. 3.2. later
  4. 4. 一些例子
    1. 4.1. 无关痛痒
    2. 4.2. 单独列一个吧
    3. 4.3. 隐蔽很深?
  5. 5. 参考