2012年8月3日星期五

Qt内存管理:从close()函数说起

(这篇文章原发表于CSDN,CSDN密码泄露事件后就删除了,现重发于这里。)

上次修改QtWin类时(见《学习笔记:windows下qt4程序实现Aero效果及动态切换》),为了使WindowNotifier 类的实例能在程序退出时自动回收内存,在其构造函数中加入了这样几行代码:
setAttribute(Qt::WA_QuitOnClose, false);
connect(qApp, SIGNAL(lastWindowClosed()), this, SLOT(close()));
setAttribute(Qt::WA_DeleteOnClose);
为什么要加入这么几句呢?我们知道,凡是继承自QObject的对象,在其构造函数中一般都有一个parent参数,对象创建时会将自己添加到parent的一个列表中,成为其child。当parent析构时会遍历child列表,将表中对象deleteQWidget继承自QObject自然也有这个参数
指定了parentQWidget对象会成为一个普通窗口部件,状态随parent变化(例如parent隐藏时它也被隐藏)。Parent0QWidget对象自动成为顶级窗口,一般带有标题栏、最小化、最大化和关闭按钮,由窗口管理器管理。当用户点击某个顶级窗口的关闭按钮时,相当于调用了它的close()函数。

      QWidget::close()作用流程如下(实质调用了QWidgetPrivate::close_helper()):
1、 调用QApplication::sendEvent()函数发出QCloseEvent事件,如果被拒绝,则close()函数返回false,窗口保持原样,接下来的几个步骤不会发生。很多软件都能在用户点击关闭按钮时弹出对话框要求确认或取消,就是通过实现虚函数QWidget::closeEvent实现的。
2、 接下来检查是否顶级窗口。如果是顶级窗口,并且是剩下的唯一一个设置了Qt::WA_QuitOnClose属性的顶级窗口时,将会调用QApplicationPrivate::emitLastWindowClosed()。这个函数将会分发一个QEvent::Quit事件,然后发出lastWindowClosed()信号。QEvent::Quit事件将引起qAppquit()被调用,从而退出事件循环。(见QCoreApplication::event(QEvent *e)
3、 接下来检查是否设置了Qt::WA_DeleteOnClose属性,如果为true则将窗体自身delete
4、 如果窗体被析构,将会在析构时遍历其child列表,将child全部delete

    综上可以看出:普通窗口部件在创建时要指定parent,这样就不用手工去回收它的内存了;或者设置Qt::WA_DeleteOnClose属性,这样会在析构时自动回收。 顶级窗口只有当其可见才会在QApplication对象析构时自动调用close()函数,但动态创建的顶级窗口仍需自己回收内存,最好设置其Qt::WA_DeleteOnClose属性。(见QApplication::closeAllWindows()函数) 
      接下来可以回到开头的问题了。QtWin类使用一个动态创建的WindowNotifier 对象作为后台监视器来接收来自窗口管理器的消息,这个对象是不可见的顶级窗口。这就导致了当其它所有可见窗口都由用户关闭时,仍然有窗口(WindowNotifier 对象)未析构,存在内存泄漏的风险。为了能回收顶级窗口的内存,通常的做法是设置其Qt::WA_DeleteOnClose属性,使其在被close()后自动析构。然而这个WindowNotifier 对象是不可见的,自然无法由用户点击关闭按钮来触发其close()函数,因此使用connect(qApp, SIGNAL(lastWindowClosed()), this, SLOT(close()))来使其在顶级窗口都关闭时自动调用close()函数。由于这个WindowNotifier 对象也是顶级窗口,故还要使用setAttribute(Qt::WA_QuitOnClose, false)来取消其Qt::WA_QuitOnClose属性。这样在上面步骤3中就会忽略这个WindowNotifier 对象而直接调用QApplicationPrivate::emitLastWindowClosed(),发出lastWindowClosed()信号了。

文档信息